Reputation: 9445
My final goal is to iterate in a clean way over all the parameters used in the last command line in bash, in order to find any path to a directory. Example of what I would wish:
$ cp some_file.txt /some/existing/folder; touch a_new_file.txt
$ my_find_func
Found "/some/existing/folder" in the last command.
My problem is about splitting the last command in a right way, in order to handle all possible cases. Now i'm using something like this:
function myfunc()
{
last_com="$(history|tail -n2|head -n1|sed -n 's/^\s*[0-9]*\s*//p')"
eval "args=( $last_com )"
# Note: I don't care about the security issue that eval can cause
for arg in "${args[@]}"; do
echo "$arg"
done
}
I like the simplicity of using eval
in this way because it handles automatically quoted parameters, escaped spaces, glob expansion, etc... So I don't have to handle this by myself with a complicated awk
or sed
command.
It works fine with single commands, like this:
/afac/soq $ cd ..
/afac $ myfunc
cd
..
/afac $ touch "some file.txt"
/afac $ myfunc
touch
some file.txt
But obviously (because of ';' inside the array definition), it fails when I use multiple commands in a single line:
$ touch a_file; rm a_file
$ myfunc
bash: syntax error near unexpected token ';'
$ touch a_file && rm a_file
$ myfunc
bash: syntax error near unexpected token '&&'
So to make it work, I would to have to split the command string into parts when ;
, &&
or ||
is encountered, without forgetting the case when these tokens are escaped or quoted, then saying goodbye to simplicity... I don't even know if I'm able yet to parse this in a good way, with the current knowledge I have about sed
and awk
...
What would be the most clean (and easiest) solution to get all the parameters of a command into an array, handling the possibility of quoted parameters, escaped characters, and multiple commands on a single-line?
It's maybe quite duplicate, but I didn't find any real solution anywhere.
Upvotes: 1
Views: 128
Reputation: 5315
Right now I am only able to produce something like this:
#!/bin/bash
last_cmd='echo 1 2 "3 4" & && echo "A B" C || echo D "E F" & '
# Convert any of: &, && or || to semicolon
cmd=$(sed -e 's/&\?[ ]*&&/;/g' -e 's/&\?[ ]*||/;/g' -e 's/&[ ]*$//' <<< "$last_cmd")
# TODO: get rid of/convert any other symbols creating issues to eval
echo "processed cmd: $cmd"
# split the command string using semicolon as delimiter
IFS=';'
cmd_arr=($cmd)
IFS=' '
args=()
for onecmd in "${cmd_arr[@]}"; do
eval "args+=($onecmd)"
done
for arg in "${args[@]}"; do
echo "$arg"
done
Version 2
last_cmd='echo 1 2 "3 4" && echo "A B" C || echo D ["E F"] $!%#^* '
# Remove ALL special characters not having a chance to appear in a pathname
cmd=$(sed 's/[][|*^#+&$!%]//g' <<< "$last_cmd")
echo "processed cmd: $cmd"
IFS=' ' eval "args=($cmd)"
for arg in "${args[@]}"; do
echo "$arg"
done
Upvotes: 1
Reputation: 12255
You can do a bit better, though not all cases, with:
function myfunc(){
set -- $(history 2 | sed 's/[ 0-9]*//;1q')
for arg
do echo "$arg"
done
}
Upvotes: 0