Running a command with bash -c vs without

What is the difference between these two commands?

  1. cat test.txt
  2. bash -c "cat test.txt"

I already read the documentation but there was not any example to understand it.


Solution 1:

cat test.txt in the shell tells it to execute the command cat with the argument test.txt

bash -c "cat text.txt" tells it to execute the command bash with the arguments -c and cat test.txt. The latter obviously ends up repeating what was stated above once the new shell instance has been loaded.

(I believe Bash has some internal optimizations to somewhat simplify what actually happens when you ask it to run a second copy of itself, but I guess that's beside the point here really.)

There are situations where you need to specify a single command for some reason (maybe a hook in another program lets you run a command, but only one) and you need to run more than one command; or when you want to use shell features like redirection or wildcard expansion in a separate shell instance.

sudo bash -c "whoami; test [ -w / ] && echo 'Fun to be privileged!'"
sudo bash -c 'echo 1 >/proc/privileged/something'  # though see also "sudo tee"
find . -name '*.txt' -exec bash -c 'mv "$1" "${1/.txt/.csv}"' _ {} \;

The final example uses the Bash-only parameter expansion ${variable/pattern/replacement} to change .txt to .csv; neither find nor mv has any facility for doing this sort of replacement, so calling a shell to do it is easy and convenient (though maybe first check if you have a rename command which would be able to do it in a single simple command like rename '/\.txt$/.csv/' **/*.txt)

Also, if you are running another shell, but want to use Bash for something (there are many Bash-only features which are handy; see also Difference between sh and bash), this is a way to do that; though if you are doing this in a script, obviously then it would usually make more sense to write the entire script in Bash.

In isolation, just running cat test.txt in a subshell is merely wasteful, and costs you an extra process for no actual benefit.

Perhaps tangentially note also that the double quotes cause the calling shell to perform variable substitutions in the quoted string before passing it as an argument to the executable. Single quotes are probably better if you don't specifically require this behavior.

Notice also that

bash -c ./script foo bar baz

is not equivalent to

./script foo bar baz

In fact, the former puts foo in "$0"! The general convention if you want to pass in arguments with bash -c is to put _ or bash in the first argument.

bash -c ./script bash foo bar baz

Another convention to be aware of is that login puts a dash in front when the shell is interactive, so that you can check whether you are running an interactive shell simply by checking whether $0 starts with -.