3

I have a bash script to run a command across multiple directories. This works fine when called like so:

$ ./run git status

However when quoting the sub-command, I get 'Command not found' errors.

$ ./run "git status"

This limits me to only being able to pass one command to be run as opposed to the ideal for example:

$ ./run "git status && npm install"

run

#!/bin/bash

paths="/Users/guy/project /Users/guy/project-2"

for i in $paths; do
    (cd "$i" && "$@")
done
  • 3
    Post your script (run) – h3rrmiller Sep 14 '17 at 15:08
6

Your run command probably expects a command to execute as opposed to shell code to evaluate (edit now that you've posted it: yes, it does, that 's "$@")

If that run command is actually a shell script it might accept builtin commands like eval (edit: yes, it does), in which case you should be able to do:

./run eval 'git status && npm install'

(that code would be evaluated in the context of the run script which could have unexpected effects if it modifies some variables internal to the script for instance (like ./run eval 'PATH=/none; ...') (edit: not here as it's the last command run in a subshell)).

If not, you can always tell it to run a shell to evaluate that shell code:

./run sh -c 'git status && npm install'

Here, I'd also use an array variable to store a list of paths instead of storing it in a scalar variable and rely on the split+glob operator to split it. You may also want to report failures in the exit status

#!/bin/bash -
paths=(/Users/guy/project /Users/guy/project-2)
ret=0

for i in "${paths[@]}"; do
  (cd -P -- "$i" && "$@") || ret=$?
done
exit "$ret"

If you wanted your script to accept shell code instead, you'd do:

#!/bin/bash -
paths=(/Users/guy/project /Users/guy/project-2)
ret=0

for i in "${paths[@]}"; do
  (cd -P -- "$i" && eval "$@") || ret=$?
done
exit "$ret"

But I would advise against doing that, as the user may be tempted to do things like:

./run git add "$file"

which would be a hidden command injection vulnerability (like when $file is foo;reboot).

./run eval "git add $file"

would be the same command injection vulnerability, but at least it wouldn't be hidden to the user.

For it not to be a command injection vulnerability, with the variant of run not using eval:

./run git add "$file"

or using eval, but with static code and passing the dynamic value via an environment variable.

FILE=$file ./run eval 'git add "$FILE" && git commit -m blah'

or using sh -c but with static code and passing the dynamic value via an argument.

./run sh -c 'git add "$1" && git commit -m blah' sh "$file"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.