We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bug in external command engine, resulting in engine crash.
**Version of Searx, commit number if you are using on master branch and stipulate if you forked Searx**
```
commit a0ddc27766271428d6f1f906c774cf6f5ccbf3fa (HEAD -> master)
Merge: 8c887382 cdceec1c
Author: Searx Service Account <searx@leandra.virtadpt.net>
Date:   Sat Dec 5 17:21:41 2020 -0800

    Merge branch 'master' of https://github.com/asciimoo/searx
```
**How did you install Searx?**
Installed using instructions from the official wiki, by hand.

**What happened?**
I went to the Searx page on my server and ran the query `!locate art bell mp3` after enabling the Locate search engine in `/opt/searx/searx/searx/settings.yml`.

**How To Reproduce**
Enable the Locate search engine in settings.yml thusly:

```
  - name: locate
    engine: command
    command: ['locate', '--existing', '--ignore-case', '{{QUERY}}']
    shortcut: locate
    tokens: []
    disabled: False
    delimiter:
        chars: ' '
        keys: ['line']
```
Restart Searx.

Execute a `!locate` search while watching Searx's output, either by running it by hand or using `journalctl -xf` to tail the systemd journal.

**Expected behavior**
Searx runs the `locate` command on the server and returns the results.

**Screenshots & Logs**
Logs (datestamp, hostname, and PID elided to prevent having to scroll back and forth repeatedly):
```
: Traceback (most recent call last):
:   File "/opt/searx/searx/searx/search.py", line 281, in search_one_offline_request_safe
:     search_results = search_one_offline_request(engine, query, request_params)
:   File "/opt/searx/searx/searx/search.py", line 274, in search_one_offline_request
:     return engine.search(query, request_params)
:   File "/opt/searx/searx/searx/engines/command.py", line 70, in search
:     cmd = _get_command_to_run(query)
:   File "/opt/searx/searx/searx/engines/command.py", line 83, in _get_command_to_run
:     params = shlex_split(query.decode('utf-8'))
: AttributeError: 'str' object has no attribute 'decode'
```

**Additional context**
Searx is being run as a system service, through systemd, with a searx.sh shell script:

```
#!/usr/bin/env bash

SEARX=/opt/searx/searx

# Change to the Searx installation directory.
cd $SEARX

# Initialize the Python virtual environment.
. env/bin/activate

# Start up Searx.
#python searx/webapp.py
uwsgi --ini searx.ini
```

Searx is being run with uwsgi to improve responsiveness.

Other searches on this instance are not impacted in this manner.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `searx/engines/command.py`
Content:
```
1 '''
2 searx is free software: you can redistribute it and/or modify
3 it under the terms of the GNU Affero General Public License as published by
4 the Free Software Foundation, either version 3 of the License, or
5 (at your option) any later version.
6 
7 searx is distributed in the hope that it will be useful,
8 but WITHOUT ANY WARRANTY; without even the implied warranty of
9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 GNU Affero General Public License for more details.
11 
12 You should have received a copy of the GNU Affero General Public License
13 along with searx. If not, see < http://www.gnu.org/licenses/ >.
14 '''
15 
16 
17 import re
18 from os.path import expanduser, isabs, realpath, commonprefix
19 from shlex import split as shlex_split
20 from subprocess import Popen, PIPE
21 from threading import Thread
22 
23 from searx import logger
24 
25 
26 offline = True
27 paging = True
28 command = []
29 delimiter = {}
30 parse_regex = {}
31 query_type = ''
32 query_enum = []
33 environment_variables = {}
34 working_dir = realpath('.')
35 result_separator = '\n'
36 result_template = 'key-value.html'
37 timeout = 4.0
38 
39 _command_logger = logger.getChild('command')
40 _compiled_parse_regex = {}
41 
42 
43 def init(engine_settings):
44     check_parsing_options(engine_settings)
45 
46     if 'command' not in engine_settings:
47         raise ValueError('engine command : missing configuration key: command')
48 
49     global command, working_dir, result_template, delimiter, parse_regex, timeout, environment_variables
50 
51     command = engine_settings['command']
52 
53     if 'working_dir' in engine_settings:
54         working_dir = engine_settings['working_dir']
55         if not isabs(engine_settings['working_dir']):
56             working_dir = realpath(working_dir)
57 
58     if 'parse_regex' in engine_settings:
59         parse_regex = engine_settings['parse_regex']
60         for result_key, regex in parse_regex.items():
61             _compiled_parse_regex[result_key] = re.compile(regex, flags=re.MULTILINE)
62     if 'delimiter' in engine_settings:
63         delimiter = engine_settings['delimiter']
64 
65     if 'environment_variables' in engine_settings:
66         environment_variables = engine_settings['environment_variables']
67 
68 
69 def search(query, params):
70     cmd = _get_command_to_run(query)
71     if not cmd:
72         return []
73 
74     results = []
75     reader_thread = Thread(target=_get_results_from_process, args=(results, cmd, params['pageno']))
76     reader_thread.start()
77     reader_thread.join(timeout=timeout)
78 
79     return results
80 
81 
82 def _get_command_to_run(query):
83     params = shlex_split(query.decode('utf-8'))
84     __check_query_params(params)
85 
86     cmd = []
87     for c in command:
88         if c == '{{QUERY}}':
89             cmd.extend(params)
90         else:
91             cmd.append(c)
92 
93     return cmd
94 
95 
96 def _get_results_from_process(results, cmd, pageno):
97     leftover = ''
98     count = 0
99     start, end = __get_results_limits(pageno)
100     with Popen(cmd, stdout=PIPE, stderr=PIPE, env=environment_variables) as process:
101         line = process.stdout.readline()
102         while line:
103             buf = leftover + line.decode('utf-8')
104             raw_results = buf.split(result_separator)
105             if raw_results[-1]:
106                 leftover = raw_results[-1]
107             raw_results = raw_results[:-1]
108 
109             for raw_result in raw_results:
110                 result = __parse_single_result(raw_result)
111                 if result is None:
112                     _command_logger.debug('skipped result:', raw_result)
113                     continue
114 
115                 if start <= count and count <= end:
116                     result['template'] = result_template
117                     results.append(result)
118 
119                 count += 1
120                 if end < count:
121                     return results
122 
123             line = process.stdout.readline()
124 
125         return_code = process.wait(timeout=timeout)
126         if return_code != 0:
127             raise RuntimeError('non-zero return code when running command', cmd, return_code)
128 
129 
130 def __get_results_limits(pageno):
131     start = (pageno - 1) * 10
132     end = start + 9
133     return start, end
134 
135 
136 def __check_query_params(params):
137     if not query_type:
138         return
139 
140     if query_type == 'path':
141         query_path = params[-1]
142         query_path = expanduser(query_path)
143         if commonprefix([realpath(query_path), working_dir]) != working_dir:
144             raise ValueError('requested path is outside of configured working directory')
145     elif query_type == 'enum' and len(query_enum) > 0:
146         for param in params:
147             if param not in query_enum:
148                 raise ValueError('submitted query params is not allowed', param, 'allowed params:', query_enum)
149 
150 
151 def check_parsing_options(engine_settings):
152     """ Checks if delimiter based parsing or regex parsing is configured correctly """
153 
154     if 'delimiter' not in engine_settings and 'parse_regex' not in engine_settings:
155         raise ValueError('failed to init settings for parsing lines: missing delimiter or parse_regex')
156     if 'delimiter' in engine_settings and 'parse_regex' in engine_settings:
157         raise ValueError('failed to init settings for parsing lines: too many settings')
158 
159     if 'delimiter' in engine_settings:
160         if 'chars' not in engine_settings['delimiter'] or 'keys' not in engine_settings['delimiter']:
161             raise ValueError
162 
163 
164 def __parse_single_result(raw_result):
165     """ Parses command line output based on configuration """
166 
167     result = {}
168 
169     if delimiter:
170         elements = raw_result.split(delimiter['chars'], maxsplit=len(delimiter['keys']) - 1)
171         if len(elements) != len(delimiter['keys']):
172             return {}
173         for i in range(len(elements)):
174             result[delimiter['keys'][i]] = elements[i]
175 
176     if parse_regex:
177         for result_key, regex in _compiled_parse_regex.items():
178             found = regex.search(raw_result)
179             if not found:
180                 return {}
181             result[result_key] = raw_result[found.start():found.end()]
182 
183     return result
184
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/searx/engines/command.py b/searx/engines/command.py
--- a/searx/engines/command.py
+++ b/searx/engines/command.py
@@ -80,7 +80,7 @@
 
 
 def _get_command_to_run(query):
-    params = shlex_split(query.decode('utf-8'))
+    params = shlex_split(query)
     __check_query_params(params)
 
     cmd = []

{"golden_diff": "diff --git a/searx/engines/command.py b/searx/engines/command.py\n--- a/searx/engines/command.py\n+++ b/searx/engines/command.py\n@@ -80,7 +80,7 @@\n \n \n def _get_command_to_run(query):\n-    params = shlex_split(query.decode('utf-8'))\n+    params = shlex_split(query)\n     __check_query_params(params)\n \n     cmd = []\n", "issue": "Bug in external command engine, resulting in engine crash.\n**Version of Searx, commit number if you are using on master branch and stipulate if you forked Searx**\r\n```\r\ncommit a0ddc27766271428d6f1f906c774cf6f5ccbf3fa (HEAD -> master)\r\nMerge: 8c887382 cdceec1c\r\nAuthor: Searx Service Account <searx@leandra.virtadpt.net>\r\nDate:   Sat Dec 5 17:21:41 2020 -0800\r\n\r\n    Merge branch 'master' of https://github.com/asciimoo/searx\r\n```\r\n**How did you install Searx?**\r\nInstalled using instructions from the official wiki, by hand.\r\n\r\n**What happened?**\r\nI went to the Searx page on my server and ran the query `!locate art bell mp3` after enabling the Locate search engine in `/opt/searx/searx/searx/settings.yml`.\r\n\r\n**How To Reproduce**\r\nEnable the Locate search engine in settings.yml thusly:\r\n\r\n```\r\n  - name: locate\r\n    engine: command\r\n    command: ['locate', '--existing', '--ignore-case', '{{QUERY}}']\r\n    shortcut: locate\r\n    tokens: []\r\n    disabled: False\r\n    delimiter:\r\n        chars: ' '\r\n        keys: ['line']\r\n```\r\nRestart Searx.\r\n\r\nExecute a `!locate` search while watching Searx's output, either by running it by hand or using `journalctl -xf` to tail the systemd journal.\r\n\r\n**Expected behavior**\r\nSearx runs the `locate` command on the server and returns the results.\r\n\r\n**Screenshots & Logs**\r\nLogs (datestamp, hostname, and PID elided to prevent having to scroll back and forth repeatedly):\r\n```\r\n: Traceback (most recent call last):\r\n:   File \"/opt/searx/searx/searx/search.py\", line 281, in search_one_offline_request_safe\r\n:     search_results = search_one_offline_request(engine, query, request_params)\r\n:   File \"/opt/searx/searx/searx/search.py\", line 274, in search_one_offline_request\r\n:     return engine.search(query, request_params)\r\n:   File \"/opt/searx/searx/searx/engines/command.py\", line 70, in search\r\n:     cmd = _get_command_to_run(query)\r\n:   File \"/opt/searx/searx/searx/engines/command.py\", line 83, in _get_command_to_run\r\n:     params = shlex_split(query.decode('utf-8'))\r\n: AttributeError: 'str' object has no attribute 'decode'\r\n```\r\n\r\n**Additional context**\r\nSearx is being run as a system service, through systemd, with a searx.sh shell script:\r\n\r\n```\r\n#!/usr/bin/env bash\r\n\r\nSEARX=/opt/searx/searx\r\n\r\n# Change to the Searx installation directory.\r\ncd $SEARX\r\n\r\n# Initialize the Python virtual environment.\r\n. env/bin/activate\r\n\r\n# Start up Searx.\r\n#python searx/webapp.py\r\nuwsgi --ini searx.ini\r\n```\r\n\r\nSearx is being run with uwsgi to improve responsiveness.\r\n\r\nOther searches on this instance are not impacted in this manner.\n", "before_files": [{"content": "'''\nsearx is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nsearx is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU Affero General Public License for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with searx. If not, see < http://www.gnu.org/licenses/ >.\n'''\n\n\nimport re\nfrom os.path import expanduser, isabs, realpath, commonprefix\nfrom shlex import split as shlex_split\nfrom subprocess import Popen, PIPE\nfrom threading import Thread\n\nfrom searx import logger\n\n\noffline = True\npaging = True\ncommand = []\ndelimiter = {}\nparse_regex = {}\nquery_type = ''\nquery_enum = []\nenvironment_variables = {}\nworking_dir = realpath('.')\nresult_separator = '\\n'\nresult_template = 'key-value.html'\ntimeout = 4.0\n\n_command_logger = logger.getChild('command')\n_compiled_parse_regex = {}\n\n\ndef init(engine_settings):\n    check_parsing_options(engine_settings)\n\n    if 'command' not in engine_settings:\n        raise ValueError('engine command : missing configuration key: command')\n\n    global command, working_dir, result_template, delimiter, parse_regex, timeout, environment_variables\n\n    command = engine_settings['command']\n\n    if 'working_dir' in engine_settings:\n        working_dir = engine_settings['working_dir']\n        if not isabs(engine_settings['working_dir']):\n            working_dir = realpath(working_dir)\n\n    if 'parse_regex' in engine_settings:\n        parse_regex = engine_settings['parse_regex']\n        for result_key, regex in parse_regex.items():\n            _compiled_parse_regex[result_key] = re.compile(regex, flags=re.MULTILINE)\n    if 'delimiter' in engine_settings:\n        delimiter = engine_settings['delimiter']\n\n    if 'environment_variables' in engine_settings:\n        environment_variables = engine_settings['environment_variables']\n\n\ndef search(query, params):\n    cmd = _get_command_to_run(query)\n    if not cmd:\n        return []\n\n    results = []\n    reader_thread = Thread(target=_get_results_from_process, args=(results, cmd, params['pageno']))\n    reader_thread.start()\n    reader_thread.join(timeout=timeout)\n\n    return results\n\n\ndef _get_command_to_run(query):\n    params = shlex_split(query.decode('utf-8'))\n    __check_query_params(params)\n\n    cmd = []\n    for c in command:\n        if c == '{{QUERY}}':\n            cmd.extend(params)\n        else:\n            cmd.append(c)\n\n    return cmd\n\n\ndef _get_results_from_process(results, cmd, pageno):\n    leftover = ''\n    count = 0\n    start, end = __get_results_limits(pageno)\n    with Popen(cmd, stdout=PIPE, stderr=PIPE, env=environment_variables) as process:\n        line = process.stdout.readline()\n        while line:\n            buf = leftover + line.decode('utf-8')\n            raw_results = buf.split(result_separator)\n            if raw_results[-1]:\n                leftover = raw_results[-1]\n            raw_results = raw_results[:-1]\n\n            for raw_result in raw_results:\n                result = __parse_single_result(raw_result)\n                if result is None:\n                    _command_logger.debug('skipped result:', raw_result)\n                    continue\n\n                if start <= count and count <= end:\n                    result['template'] = result_template\n                    results.append(result)\n\n                count += 1\n                if end < count:\n                    return results\n\n            line = process.stdout.readline()\n\n        return_code = process.wait(timeout=timeout)\n        if return_code != 0:\n            raise RuntimeError('non-zero return code when running command', cmd, return_code)\n\n\ndef __get_results_limits(pageno):\n    start = (pageno - 1) * 10\n    end = start + 9\n    return start, end\n\n\ndef __check_query_params(params):\n    if not query_type:\n        return\n\n    if query_type == 'path':\n        query_path = params[-1]\n        query_path = expanduser(query_path)\n        if commonprefix([realpath(query_path), working_dir]) != working_dir:\n            raise ValueError('requested path is outside of configured working directory')\n    elif query_type == 'enum' and len(query_enum) > 0:\n        for param in params:\n            if param not in query_enum:\n                raise ValueError('submitted query params is not allowed', param, 'allowed params:', query_enum)\n\n\ndef check_parsing_options(engine_settings):\n    \"\"\" Checks if delimiter based parsing or regex parsing is configured correctly \"\"\"\n\n    if 'delimiter' not in engine_settings and 'parse_regex' not in engine_settings:\n        raise ValueError('failed to init settings for parsing lines: missing delimiter or parse_regex')\n    if 'delimiter' in engine_settings and 'parse_regex' in engine_settings:\n        raise ValueError('failed to init settings for parsing lines: too many settings')\n\n    if 'delimiter' in engine_settings:\n        if 'chars' not in engine_settings['delimiter'] or 'keys' not in engine_settings['delimiter']:\n            raise ValueError\n\n\ndef __parse_single_result(raw_result):\n    \"\"\" Parses command line output based on configuration \"\"\"\n\n    result = {}\n\n    if delimiter:\n        elements = raw_result.split(delimiter['chars'], maxsplit=len(delimiter['keys']) - 1)\n        if len(elements) != len(delimiter['keys']):\n            return {}\n        for i in range(len(elements)):\n            result[delimiter['keys'][i]] = elements[i]\n\n    if parse_regex:\n        for result_key, regex in _compiled_parse_regex.items():\n            found = regex.search(raw_result)\n            if not found:\n                return {}\n            result[result_key] = raw_result[found.start():found.end()]\n\n    return result\n", "path": "searx/engines/command.py"}], "after_files": [{"content": "'''\nsearx is free software: you can redistribute it and/or modify\nit under the terms of the GNU Affero General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nsearx is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU Affero General Public License for more details.\n\nYou should have received a copy of the GNU Affero General Public License\nalong with searx. If not, see < http://www.gnu.org/licenses/ >.\n'''\n\n\nimport re\nfrom os.path import expanduser, isabs, realpath, commonprefix\nfrom shlex import split as shlex_split\nfrom subprocess import Popen, PIPE\nfrom threading import Thread\n\nfrom searx import logger\n\n\noffline = True\npaging = True\ncommand = []\ndelimiter = {}\nparse_regex = {}\nquery_type = ''\nquery_enum = []\nenvironment_variables = {}\nworking_dir = realpath('.')\nresult_separator = '\\n'\nresult_template = 'key-value.html'\ntimeout = 4.0\n\n_command_logger = logger.getChild('command')\n_compiled_parse_regex = {}\n\n\ndef init(engine_settings):\n    check_parsing_options(engine_settings)\n\n    if 'command' not in engine_settings:\n        raise ValueError('engine command : missing configuration key: command')\n\n    global command, working_dir, result_template, delimiter, parse_regex, timeout, environment_variables\n\n    command = engine_settings['command']\n\n    if 'working_dir' in engine_settings:\n        working_dir = engine_settings['working_dir']\n        if not isabs(engine_settings['working_dir']):\n            working_dir = realpath(working_dir)\n\n    if 'parse_regex' in engine_settings:\n        parse_regex = engine_settings['parse_regex']\n        for result_key, regex in parse_regex.items():\n            _compiled_parse_regex[result_key] = re.compile(regex, flags=re.MULTILINE)\n    if 'delimiter' in engine_settings:\n        delimiter = engine_settings['delimiter']\n\n    if 'environment_variables' in engine_settings:\n        environment_variables = engine_settings['environment_variables']\n\n\ndef search(query, params):\n    cmd = _get_command_to_run(query)\n    if not cmd:\n        return []\n\n    results = []\n    reader_thread = Thread(target=_get_results_from_process, args=(results, cmd, params['pageno']))\n    reader_thread.start()\n    reader_thread.join(timeout=timeout)\n\n    return results\n\n\ndef _get_command_to_run(query):\n    params = shlex_split(query)\n    __check_query_params(params)\n\n    cmd = []\n    for c in command:\n        if c == '{{QUERY}}':\n            cmd.extend(params)\n        else:\n            cmd.append(c)\n\n    return cmd\n\n\ndef _get_results_from_process(results, cmd, pageno):\n    leftover = ''\n    count = 0\n    start, end = __get_results_limits(pageno)\n    with Popen(cmd, stdout=PIPE, stderr=PIPE, env=environment_variables) as process:\n        line = process.stdout.readline()\n        while line:\n            buf = leftover + line.decode('utf-8')\n            raw_results = buf.split(result_separator)\n            if raw_results[-1]:\n                leftover = raw_results[-1]\n            raw_results = raw_results[:-1]\n\n            for raw_result in raw_results:\n                result = __parse_single_result(raw_result)\n                if result is None:\n                    _command_logger.debug('skipped result:', raw_result)\n                    continue\n\n                if start <= count and count <= end:\n                    result['template'] = result_template\n                    results.append(result)\n\n                count += 1\n                if end < count:\n                    return results\n\n            line = process.stdout.readline()\n\n        return_code = process.wait(timeout=timeout)\n        if return_code != 0:\n            raise RuntimeError('non-zero return code when running command', cmd, return_code)\n\n\ndef __get_results_limits(pageno):\n    start = (pageno - 1) * 10\n    end = start + 9\n    return start, end\n\n\ndef __check_query_params(params):\n    if not query_type:\n        return\n\n    if query_type == 'path':\n        query_path = params[-1]\n        query_path = expanduser(query_path)\n        if commonprefix([realpath(query_path), working_dir]) != working_dir:\n            raise ValueError('requested path is outside of configured working directory')\n    elif query_type == 'enum' and len(query_enum) > 0:\n        for param in params:\n            if param not in query_enum:\n                raise ValueError('submitted query params is not allowed', param, 'allowed params:', query_enum)\n\n\ndef check_parsing_options(engine_settings):\n    \"\"\" Checks if delimiter based parsing or regex parsing is configured correctly \"\"\"\n\n    if 'delimiter' not in engine_settings and 'parse_regex' not in engine_settings:\n        raise ValueError('failed to init settings for parsing lines: missing delimiter or parse_regex')\n    if 'delimiter' in engine_settings and 'parse_regex' in engine_settings:\n        raise ValueError('failed to init settings for parsing lines: too many settings')\n\n    if 'delimiter' in engine_settings:\n        if 'chars' not in engine_settings['delimiter'] or 'keys' not in engine_settings['delimiter']:\n            raise ValueError\n\n\ndef __parse_single_result(raw_result):\n    \"\"\" Parses command line output based on configuration \"\"\"\n\n    result = {}\n\n    if delimiter:\n        elements = raw_result.split(delimiter['chars'], maxsplit=len(delimiter['keys']) - 1)\n        if len(elements) != len(delimiter['keys']):\n            return {}\n        for i in range(len(elements)):\n            result[delimiter['keys'][i]] = elements[i]\n\n    if parse_regex:\n        for result_key, regex in _compiled_parse_regex.items():\n            found = regex.search(raw_result)\n            if not found:\n                return {}\n            result[result_key] = raw_result[found.start():found.end()]\n\n    return result\n", "path": "searx/engines/command.py"}]}