Reputation: 6633
I am trying to execute a shell script like below
cmds=('uptime' 'free -m' 'nproc')
for cmd in ${cmds[@]};
do
echo $($cmd)
done
Execution is breaking when it comes to free -m
execution because of space.
vagrant@vagrant-ubuntu-trusty-64:~$ bash for_my_script.sh
03:42:50 up 56 min, 1 user, load average: 0.00, 0.00, 0.00
total used free shared buff/cache available
Mem: 499928 108516 43204 1844 348208 366140
Swap: 0 0 0
for_my_script.sh: line 5: -m: command not found
1
vagrant@vagrant-ubuntu-trusty-64:~$
I tried iterating with for by storing commands in variable
vagrant@vagrant-ubuntu-trusty-64:~$ cmds="uptime,free -m"
vagrant@vagrant-ubuntu-trusty-64:~$ for cmd in "${cmds//,/ }"; do echo "$($cmd)"; done
uptime: invalid option -- 'm'
vagrant@vagrant-ubuntu-trusty-64:~$ cmds="uptime,'free -m'"
vagrant@vagrant-ubuntu-trusty-64:~$ for cmd in "${cmds//,/ }"; do echo "$($cmd)"; done
uptime: invalid option -- 'm'
With no success.
is touching IFS
is the only way for this type of problem?
any inputs are much appreciated.
Upvotes: 0
Views: 256
Reputation: 48794
The (edit: previously) accepted answer still involves unquoted strings, which are discouraged and error-prone. You could use eval
as @Fravadona suggests, but eval
is also hard to work with properly/safely.
Instead, consider creating functions to wrap the commands you want to run; then you don't need to deal with nested arguments within strings. Functions also makes it easy to compose more complex behavior (pipelines, conditionals, etc.) without complicating the execution loop. Something like:
do_free() { free -m; }
cmds=(uptime do_free nproc)
for cmd in "${cmds[@]}"; do
"$cmd" # notice that you don't need echo $("$cmd"); it's generally redundant
done
You could create wrappers for uptime
and nproc
too for consistency, but it's not necessary as long as you don't need to pass any arguments.
For a more complete example of what I'm suggesting see this heartbeat script and the COMMANDS
array in particular.
Upvotes: 3
Reputation: 16865
In this specific case the right way would be to use eval
:
cmds=('uptime' 'free -m' 'nproc')
for cmd in "${cmds[@]}"
do
eval "$cmd"
done
But you need to make sure that the content of cmd
is exactly like you would type it in the terminal
eval
is evil, when you don't know the right way to use it
Let's see a few examples:
# cmd='echo "a b"'
#
# eval "$cmd"
a b
#
# $cmd
"a b"
#
# cmd='echo a; echo b'
#
# eval "$cmd"
a
b
# $cmd
a; echo b
#
# cmd='echo;'
#
# eval "$cmd"
# $cmd
bash: echo;: command not found...
#
Upvotes: 1