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:
- Run
cat
with no parameters and ctrl+c to interupt. - 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
How to Read Single Character Input from Keyboard Using Nasm (Assembly) Under Ubuntu
Bash Copy All Files Except One
Joining Multiple Fields in Text Files on Unix
How to Set Memory Limit for Oom Killer for Chrome
How to Use Gdb to Debug a Running Process
What Is Global _Start in Assembly Language
Gem5 Syscall Emulation Arm C Hello World Fails with "Fatal: Syscall Gettid (#224) Unimplemented"
Unit Testing for Shell Scripts
What Is the Purpose of Map_Anonymous Flag in Mmap System Call
Prevent Gnome Terminal from Exiting After Execution
How to Increase Neo4J's Maximum File Open Limit (Ulimit) in Ubuntu
Sorting on the Last Field of a Line
How to Have Simple and Double Quotes in a Scripted Ssh Command
Can't Clone a Github Repo on Linux via Https
How to Reference Files Relative to Application Root in Node.Js