What Does 'Bash -C' Do

What does 'bash -c' do?

Quoting from man bash:

-c string If the -c option is present, then commands are read
from string.

If there are arguments after the string, they are assigned to the positional parameters, starting with $0.

The command quoted by you would append the text in heredoc (i.e. the text in VirtualHost tag) to the file /etc/apache2/sites-available/magento-store.com.

What is the use of bash -c command?

bash -c option with ssh is one such method to execute multi line commands on the ssh server with variable expansion.

Say you have

VAR1="Variable 1"
ssh app@HOSTNAME '
ls
pwd
if true; then
echo "True"
echo $VAR1 # <-- it won't work
else
echo "False"
fi
'

But with bash -c

VAR1="Variable 1"
ssh -t "${SSH_USER}@${SERVER_IP}" bash -c "'
ls
pwd
if true; then
echo $VAR1 <-- This works
else
echo "False"
fi
'"

Running a command with bash -c vs without

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 -.

when to use bash with option -c?

bash -c isn't as interesting when you're already running bash. Consider, on the other hand, the case when you want to run bash code from a Python script:

#!/usr/bin/env python
import subprocess
fileOne='hello'
fileTwo='world'

p = subprocess.Popen(['bash', '-c', 'diff <(sort "$1") <(sort "$2")',
'_', # this is $0 inside the bash script above
fileOne, # this is $1
fileTwo, # and this is $2
])
print p.communicate() # run that bash interpreter, and print its stdout and stderr

Here, because we're using bash-only syntax (<(...)), you couldn't run this with anything that used POSIX sh by default, which is the case for subprocess.Popen(..., shell=True); using bash -c thus provides access to capabilities that wouldn't otherwise be available without playing with FIFOs yourself.


Incidentally, this isn't the only way to do that: One could also use bash -s, and pass code in on stdin. Below, that's being done not from Python but POSIX sh (/bin/sh, which likewise is not guaranteed to have <(...) available):

#!/bin/sh

# ...this is POSIX sh code, not bash code; you can't use <() here
# ...so, if we want to do that, one way is as follows:

fileOne=hello
fileTwo=world

bash -s "$fileOne" "$fileTwo" <<'EOF'
# the inside of this heredoc is bash code, not POSIX sh code
diff <(sort "$1") <(sort "$2")
EOF

What does -c mean as an argument to bash?

From the bash manual page:

bash interprets the following options when it is invoked:

-c string

If the -c option is present, then commands are read from string. If there are arguments after the string, they are assigned to the positional parameters, starting with $0.

Without the -c, the "while true..." string is taken to be a filename for bash to open.

What's the purpose of the argument at the end of bash -c command argument ?


I don't understand the purpose of the $0 assignment.

Its purpose is to let you give your inline script a name, potentially for decorating diagnostic messages. There is nothing interesting about it.

$ cat foo.sh
echo my name is $0
$
$ bash foo.sh a b c
my name is foo.sh
$
$ bash -c 'echo my name is $0' foo a b c
my name is foo

Confused with `bash -c` command - does not work with global variables

Your double quoted string is being expanded BEFORE the commands in the string are being interpreted by bash -c so that $? is likely a previous command's exit code.

You can see this with an example where we also use the -x flag to see what's happening under the hood:

$ set -x
$ bash -c "v=5; echo $v"
+ bash -c 'v=3; echo '

You can see that there is no value of $v to echo when execution hits that line as $v was already replaced before the command was interpreted by bash -c.

For further expirementation; to get nearly the same output as your issue, do the following:

  1. Run cat with no parameters and ctrl+c to interupt.
  2. Run bash -c "echo 'hi'; echo $?"

You should get hi and 130 as the output, which was the exit code of your sigint'd cat command. (note that it's not the exit 0 of the echo "hi" as you might expect).


Thankfully for your command, you can just swap out your quotes to avoid this issue:

/bin/bash -c '(while true; do \
sleep 1; \
ping -c 1 "www.google.com"; \
if [ $? -eq 0 ]; then break; fi; \
done); \
exit 0'


Related Topics



Leave a reply



Submit