What does $? give us exactly in a shell script?
$?
is a variable holding the return value of the last command you ran.
Example C program (example.c
):
int main() { return 1; }
Example Bash:
gcc -o example example.c
./example
echo $? # prints 1
What does $@ mean in a shell script?
$@
is nearly the same as $*
, both meaning "all command line arguments". They are often used to simply pass all arguments to another program (thus forming a wrapper around that other program).
The difference between the two syntaxes shows up when you have an argument with spaces in it (e.g.) and put $@
in double quotes:
wrappedProgram "$@"
# ^^^ this is correct and will hand over all arguments in the way
# we received them, i. e. as several arguments, each of them
# containing all the spaces and other uglinesses they have.
wrappedProgram "$*"
# ^^^ this will hand over exactly one argument, containing all
# original arguments, separated by single spaces.
wrappedProgram $*
# ^^^ this will join all arguments by single spaces as well and
# will then split the string as the shell does on the command
# line, thus it will split an argument containing spaces into
# several arguments.
Example: Calling
wrapper "one two three" four five "six seven"
will result in:
"$@": wrappedProgram "one two three" four five "six seven"
"$*": wrappedProgram "one two three four five six seven"
^^^^ These spaces are part of the first
argument and are not changed.
$*: wrappedProgram one two three four five six seven
Meaning of $? (dollar question mark) in shell scripts
This is the exit status of the last executed command.
For example the command true
always returns a status of 0
and false
always returns a status of 1
:
true
echo $? # echoes 0
false
echo $? # echoes 1
From the manual: (acessible by calling man bash
in your shell)
?
Expands to the exit status of the most recently executed foreground pipeline.
By convention an exit status of 0
means success, and non-zero return status means failure. Learn more about exit statuses on wikipedia.
There are other special variables like this, as you can see on this online manual: https://www.gnu.org/s/bash/manual/bash.html#Special-Parameters
what does '$?' mean in a shell script?
$?
returns the status of the last finished command.
Status 0 tells you that everything finished ok.
In addition the $
sign is a special symbol - and in that case $val
extract the value that is hold by the variable val
What are the special dollar sign shell variables?
$1
,$2
,$3
, ... are the positional parameters."$@"
is an array-like construct of all positional parameters,{$1, $2, $3 ...}
."$*"
is the IFS expansion of all positional parameters,$1 $2 $3 ...
.$#
is the number of positional parameters.$-
current options set for the shell.$$
pid of the current shell (not subshell).$_
most recent parameter (or the abs path of the command to start the current shell immediately after startup).$IFS
is the (input) field separator.$?
is the most recent foreground pipeline exit status.$!
is the PID of the most recent background command.$0
is the name of the shell or shell script.
Most of the above can be found under Special Parameters in the Bash Reference Manual. There are all the environment variables set by the shell.
For a comprehensive index, please see the Reference Manual Variable Index.
Expansion of variables inside single quotes in a command in Bash
Inside single quotes everything is preserved literally, without exception.
That means you have to close the quotes, insert something, and then re-enter again.
'before'"$variable"'after'
'before'"'"'after'
'before'\''after'
Word concatenation is simply done by juxtaposition. As you can verify, each of the above lines is a single word to the shell. Quotes (single or double quotes, depending on the situation) don't isolate words. They are only used to disable interpretation of various special characters, like whitespace, $
, ;
... For a good tutorial on quoting see Mark Reed's answer. Also relevant: Which characters need to be escaped in bash?
Do not concatenate strings interpreted by a shell
You should absolutely avoid building shell commands by concatenating variables. This is a bad idea similar to concatenation of SQL fragments (SQL injection!).
Usually it is possible to have placeholders in the command, and to supply the command together with variables so that the callee can receive them from the invocation arguments list.
For example, the following is very unsafe. DON'T DO THIS
script="echo \"Argument 1 is: $myvar\""
/bin/sh -c "$script"
If the contents of $myvar
is untrusted, here is an exploit:
myvar='foo"; echo "you were hacked'
Instead of the above invocation, use positional arguments. The following invocation is better -- it's not exploitable:
script='echo "arg 1 is: $1"'
/bin/sh -c "$script" -- "$myvar"
Note the use of single ticks in the assignment to script
, which means that it's taken literally, without variable expansion or any other form of interpretation.
what does $* mean in a shell script
It means all the arguments passed to the script or function, split by word.
It is usually wrong and should be replaced by "$@"
, which separates the arguments properly.
Are shell scripts sensitive to encoding and line endings?
Yes. Bash scripts are sensitive to line-endings, both in the script itself and in data it processes. They should have Unix-style line-endings, i.e., each line is terminated with a Line Feed character (decimal 10, hex 0A in ASCII).
DOS/Windows line endings in the script
With Windows or DOS-style line endings , each line is terminated with a Carriage Return followed by a Line Feed character. You can see this otherwise invisible character in the output of cat -v yourfile
:
$ cat -v yourfile
#!/bin/bash^M
^M
cd "src"^M
npm install^M
^M
cd ..^M
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &^M
In this case, the carriage return (^M
in caret notation or \r
in C escape notation) is not treated as whitespace. Bash interprets the first line after the shebang (consisting of a single carriage return character) as the name of a command/program to run.
- Since there is no command named
^M
, it prints: command not found
- Since there is no directory named
"src"^M
(orsrc^M
), it prints: No such file or directory
- It passes
install^M
instead ofinstall
as an argument tonpm
which causesnpm
to complain.
DOS/Windows line endings in input data
Like above, if you have an input file with carriage returns:
hello^M
world^M
then it will look completely normal in editors and when writing it to screen, but tools may produce strange results. For example, grep
will fail to find lines that are obviously there:
$ grep 'hello$' file.txt || grep -x "hello" file.txt
(no match because the line actually ends in ^M)
Appended text will instead overwrite the line because the carriage returns moves the cursor to the start of the line:
$ sed -e 's/$/!/' file.txt
!ello
!orld
String comparison will seem to fail, even though strings appear to be the same when writing to screen:
$ a="hello"; read b < file.txt
$ if [[ "$a" = "$b" ]]
then echo "Variables are equal."
else echo "Sorry, $a is not equal to $b"
fi
Sorry, hello is not equal to hello
Solutions
The solution is to convert the file to use Unix-style line endings. There are a number of ways this can be accomplished:
This can be done using the
dos2unix
program:dos2unix filename
Open the file in a capable text editor (Sublime, Notepad++, not Notepad) and configure it to save files with Unix line endings, e.g., with Vim, run the following command before (re)saving:
:set fileformat=unix
If you have a version of the
sed
utility that supports the-i
or--in-place
option, e.g., GNUsed
, you could run the following command to strip trailing carriage returns:sed -i 's/\r$//' filename
With other versions of
sed
, you could use output redirection to write to a new file. Be sure to use a different filename for the redirection target (it can be renamed later).sed 's/\r$//' filename > filename.unix
Similarly, the
tr
translation filter can be used to delete unwanted characters from its input:tr -d '\r' <filename >filename.unix
Cygwin Bash
With the Bash port for Cygwin, there’s a custom igncr
option that can be set to ignore the Carriage Return in line endings (presumably because many of its users use native Windows programs to edit their text files).
This can be enabled for the current shell by running set -o igncr
.
Setting this option applies only to the current shell process so it can be useful when sourcing files with extraneous carriage returns. If you regularly encounter shell scripts with DOS line endings and want this option to be set permanently, you could set an environment variable called SHELLOPTS
(all capital letters) to include igncr
. This environment variable is used by Bash to set shell options when it starts (before reading any startup files).
Useful utilities
The file
utility is useful for quickly seeing which line endings are used in a text file. Here’s what it prints for for each file type:
- Unix line endings:
Bourne-Again shell script, ASCII text executable
- Mac line endings:
Bourne-Again shell script, ASCII text executable, with CR line terminators
- DOS line endings:
Bourne-Again shell script, ASCII text executable, with CRLF line terminators
The GNU version of the cat
utility has a -v, --show-nonprinting
option that displays non-printing characters.
The dos2unix
utility is specifically written for converting text files between Unix, Mac and DOS line endings.
Useful links
Wikipedia has an excellent article covering the many different ways of marking the end of a line of text, the history of such encodings and how newlines are treated in different operating systems, programming languages and Internet protocols (e.g., FTP).
Files with classic Mac OS line endings
With Classic Mac OS (pre-OS X), each line was terminated with a Carriage Return (decimal 13, hex 0D in ASCII). If a script file was saved with such line endings, Bash would only see one long line like so:
#!/bin/bash^M^Mcd "src"^Mnpm install^M^Mcd ..^M./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &^M
Since this single long line begins with an octothorpe (#
), Bash treats the line (and the whole file) as a single comment.
Note: In 2001, Apple launched Mac OS X which was based on the BSD-derived NeXTSTEP operating system. As a result, OS X also uses Unix-style LF-only line endings and since then, text files terminated with a CR have become extremely rare. Nevertheless, I think it’s worthwhile to show how Bash would attempt to interpret such files.
Replace a string in shell script using a variable
If you want to interpret $replace
, you should not use single quotes since they prevent variable substitution.
Try:
echo $LINE | sed -e "s/12345678/${replace}/g"
Transcript:
pax> export replace=987654321
pax> echo X123456789X | sed "s/123456789/${replace}/"
X987654321X
pax> _
Just be careful to ensure that ${replace}
doesn't have any characters of significance to sed
(like /
for instance) since it will cause confusion unless escaped. But if, as you say, you're replacing one number with another, that shouldn't be a problem.
Related Topics
Is There Any Significant Difference Between Tcp_Cork and Tcp_Nodelay in This Use-Case
Change Filenames to Lowercase in Ubuntu in All Subdirectories
Remove Occurrences of String in Text File
Difference Between Completion Variables and Semaphores
Hook into Linux Key Event Handling
How to Identify Multiple Usb-Serial Adapters Under Ubuntu 10.1
Search Ms Word Files in a Directory for Specific Content in Linux
What Is the Best Tool to Convert Common Video Formats to Flv on a Linux Cli
Merge/Join Two Tables Fast Linux Command Line
Calculating Rounded Percentage in Shell Script Without Using "Bc"
Command to Insert Lines Before First Match
How to Overcome an Incompatibility Between the Ksh on Linux VS. That Installed on Aix/Solaris/Hpux
How to Flush Cache of Hard-Disk and Flash-Disk (Or Filesystem) from Command Line
Cmake:How to Change File Permissions When Installing
Creating a Self-Extracting Zip Archive on a Linux Box
Automatically Kill Process That Consume Too Much Memory or Stall on Linux
How to Detect a Buffer Over Run on Serial Port in Linux Using C++