Reputation: 183
In order to get eval to work on commands that contain spaces inside one of the parameters, I have only found this to work so far:
eval 'sed 's/foo/foo'" "'bar/g' filename'
In a hypothetical program where users would enter a command and then the command and arguments to be fed to eval, this isn't a very elegant or robust solution. Are there any other ways to run the eval command so that the interface for my_command can be a little more user friendly? The following is an example of how the program accepts arguments now.
my_command 'sed 's/foo/foo'" "'bar/g' filename'
I would like the interface to work something like this:
my_command sed 's/foo/foo bar/g' filename
edit:
I'll try asking a different question:
How do I get bash to read input from the command line literally? I want the exact input to be preserved, so if there are quotes I want to keep them. I can accomplish what I want to do by using egrep to read from file and then sanitizing the input, like so:
egrep '/.*/' filename |
sed 's/\(.*\)['"'"']\(.*\) \(.*\)['"'"']\(.*\)/\1'"\'"'\2" "\3'"\'"'\4/g'
with "filename" containing this line
sed 's/foo/foo bar/g' file
this gives me the desired output of:
sed 's/foo/foo" "bar/g' file
Problem here is that I can't echo "$@"
because bash interprets the quotes. I want the literal input without having to read from file.
Upvotes: 4
Views: 1352
Reputation: 46745
The following keeps spaces in arguments by quoting each element of array:
function token_quote {
local quoted=()
for token; do
quoted+=( "$(printf '%q' "$token")" )
done
printf '%s\n' "${quoted[*]}"
}
Example usage:
$ token_quote token 'single token' token
token single\ token token
Above, note the single token
's space is quoted as \
.
$ set $(token_quote token 'single token' token)
$ eval printf '%s\\n' "$@"
token
single token
token
$
This shows that the tokens are indeed kept separate.
Given some untrusted user input:
% input="Trying to hack you; date"
Construct a command to eval:
% cmd=(echo "User gave:" "$input")
Eval it, with seemingly correct quoting:
% eval "$(echo "${cmd[@]}")"
User gave: Trying to hack you
Thu Sep 27 20:41:31 +07 2018
Note you were hacked. date
was executed rather than being printed literally.
Instead with token_quote()
:
% eval "$(token_quote "${cmd[@]}")"
User gave: Trying to hack you; date
%
eval
isn't evil - it's just misunderstood :)
Upvotes: 1
Reputation: 753475
For your preferred use-case, you'd simply write (inside my_command
):
"$@"
to execute the command as given.
Your eval
line is odd:
eval 'sed 's/foo/foo'" "'bar/g' filename'
Because of the way single quotes don't nest, it is equivalent to:
eval 'sed s/foo/foo" "bar/g filename'
Possible solution:
egrep '/.*/' filename | sh
This feeds what is in filename
directly to the shell for interpretation. Given file
containing:
Some text containing foo; and bar.
More foo bar?
More text; more foo and bar; more foo bar beyond the possibility of unfooing.
The output is:
Some text containing foo bar; and bar.
More foo bar bar?
More text; more foo bar and bar; more foo bar bar beyond the possibility of unfoo baring.
Note that your complex sed
script is not complex enough. Given filename
containing:
sed 's/foo/foo bar/g' file
sed 's/foo bar/foo bar baz/g' file
the output from:
egrep '/.*/' filename |
sed 's/\(.*\)['"'"']\(.*\) \(.*\)['"'"']\(.*\)/\1'"\'"'\2" "\3'"\'"'\4/g'
is:
sed 's/foo/foo" "bar/g' file
sed 's/foo bar/foo bar" "baz/g' file
which has not solved all the problems for the eval
.
I've spent a lot of time, on and off, working on such issues over quite a long period of time (a quarter century is no exaggeration), and it isn't trivial. You can find one discussion in extenso in How to iterate over arguments in bash script. Somewhere, I have another answer which goes through gyrations about this stuff, but I can't immediately find it (where 'immediately' means an hour or so of distracted searching, where the distractions were sets of duplicate questions, etc). It may have been deleted, or I may have looked in the wrong place.
Upvotes: 6
Reputation: 233
your design is flawed. Create a user interface that doesn't let them input commands directly. give options, or let them enter the parameters only.
At the back end, you do your sanitization check on the parameters before calling sed
or other tools desired. You don't have to use eval
Upvotes: 1
Reputation: 59435
It can actually work as you desire. Use "$@"
- this will pass all the arguments exactly as they were given on the command line.
If my_command.sh contains:
sed "$@"
Then my_command.sh 's/foo/foo bar/g' filename
will do exactly what you expect.
Upvotes: 0