Passing arguments to a script
I am taking the Linux Essentials class and was doing well until I hit the scripting chapter. I simply do not understand these concepts. Wondering if someone can break the following down into ultra simplistic terms or point me to a better reference to learn it. I'm currently using netacad's curriculum.
From the text book (with minor formatting changes):
There are some special variables in addition to the ones you set. You can pass arguments to your script:
#!/bin/bash echo "Hello $1"
A dollar sign followed by a number N corresponds to the Nth argument passed to the script. If you call the example above with
./test.sh
the output will be Hello Linux. The$0
variable contains the name of the script itself.After a program runs, be it a binary or a script, it returns an exit code which is an integer between 0 and 255. You can test this through the
$?
variable to see if the previous command completed successfully.
I understand how to assign variables and how they work with the $
but the whole issue with $0
and $1
-- I just don't get it.
Any help would be much appreciated.
Solution 1:
The description from the book is wrong (or at least missing something). To get that script to print "Hello Linux", you'd run it like this:
./test.sh Linux
If you run it with just ./test.sh
, then it'll only print "Hello ", because there was no first argument and $1
is not defined. On the other hand, suppose you ran it like this:
./test.sh foo bar baz
then within the script, $0
would be "./test.sh", $1
would be "foo", $2
would be "bar", and $3
would be "baz".
As for $?
, consider the following script snippet:
ls nonexistentfile.txt
echo "The exit status of ls was: $?"
echo "The exit status of echo (the first one) was: $?"
When run that'll print something like:
ls: nonexistentfile.txt: No such file or directory
The exit status of ls was: 1
The exit status of echo (the first one) was: 0
The ls
command can't list nonexistentfile.txt ('cause it doesn't exist), so it prints an error message to that effect, and exits with a nonzero status to indicate that something went wrong. The first echo
command prints that exit status ($?
), and since it does that successfully, it exits with a status of zero. When the second echo
command runs, it gets $?
from the first echo
command, so it prints "0".
BTW, a lot of commands just use exit statuses of 0 (success) or 1 (some sort of failure), but some use different failure statuses to indicate exactly what went wrong. Here's an excerpt from the curl
manual page:
EXIT CODES
There are a bunch of different error codes and their corresponding
error messages that may appear during bad conditions. At the time of
this writing, the exit codes are:
1 Unsupported protocol. This build of curl has no support for this
protocol.
2 Failed to initialize.
3 URL malformed. The syntax was not correct.
...
88 FTP chunk callback reported error
89 No connection available, the session will be queued
90 SSL public key does not matched pinned public key
91 Invalid SSL certificate status.
92 Stream error in HTTP/2 framing layer.
...so a script that used curl
could check $?
to figure out what went wrong, and respond differently depending on the problem.
Solution 2:
$0
is the name you use to run the script. $1
, $2
, and so forth are the script's positional parameters, which hold the values of the command-line arguments you passed when you ran the script.
As Gordon Davisson said, the book's author must've meant to say that running ./test Linux
would print Hello Linux
. When you do that, ./test
goes into to the special parameter 0
, and Linux
goes to into the first positional parameter 1
. The script expands that first positional parameter by preceding it with a dollar sign ($1
), just as you do with variables. If you had instead run ./test Hello Linux for Human Beings
, then in the script, $1
would expand to Linux
, $2
to for
, $3
to Human
, and $4
to Beings
.
You can write a simple script to try this out:
#!/bin/bash
echo "\$0 expands to '$0'."
echo "\$1 expands to '$1'."
echo "\$2 expands to '$2'."
echo "\$3 expands to '$3'."
(Go on as far as you want. For positional parameters higher than 9
, use the ${
}
form of parameter expansion, e.g., expand 10
by writing ${10}
. In scripts that work with many positional parameters, the special parameter @
is often used, avoiding repetition, but you can ignore that for now if you like.)
Try saving that in a file and marking the file executable, which you can do by running chmod +x simple
where simple
is replaced with the name of the file, if different. Then you can run it using commands like ./simple
, ./simple foo
, ./simple foo bar
, and so on.
You'll notice that when fewer than three command-line arguments are passed, positional parameters that correspond to the ones that weren't passed expand to the empty string. That's what happens when you attempt to expand a shell parameter that is not defined. You'll notice, further, that when more command-line arguments are passed, the ones past the third are not used. That's probably what you'd expect, since the script doesn't refer to them at all.
Now try running ./simple *
. The shell expands *
to all the filenames in the current directory except those that start with .
, so three of those will be shown as the first three positional parameters (or fewer if there aren't that many). You can try running it with other shell expansions, such as ./simple {5..10}
.
You can pass command-line arguments containing blanks by enclosing them in quotes. For example, try ./simple 'foo bar' baz
. Notice that $1
expands to foo bar
this time, and not just to foo
.
Because the shell performs various expansions, it's not always obvious how many command-line arguments you're passing to a command. An easy way to see what each argument will be is to replace the command with printf '[%s]\n'
. For example:
$ printf '[%s]\n' f*
[fonts]
[fstab]
[fuse.conf]
[fwupd]
$ printf '[%s]\n' {1,3}{a..c}
[1a]
[1b]
[1c]
[3a]
[3b]
[3c]
Since you've only recently started shell scripting, the Bash reference manual may be challenging, and you might not want to read it from front to back. But I think it's a valuable resource even if you consider yourself a complete beginner. You may find the section on shell parameters useful, since it starts with what you already know--shell variables--and moves on to special parameters like ?
(which people often call the $?
parameter, since that's how you expand it). For general learning about Bash, especially at a more introductory level, I recommend these pages, including BashGuide.
Solution 3:
One good book you should know about is William Shotts's "The Linux Command Line", published by No Starch Press and available as a free pdf on the author's website.
In every shell script, there is a collection of numbered variables, which are generally referred to as $1
, $2
, etc. These are the "positional parameters", more commonly known as the command line arguments. You can think of these as variables named 1
, 2
, etc. and to get their values, you would use $1
, $2
, etc. When you call a script named my_script
via the command line ./my_script a b c
, it would get three arguments which are stored in the three variables $1
, $2
, and $3
. You cannot assign to these variables (except as a group), but you can examine and use their values. For example, echo "$1"
would print the first argument to your script.
$0
is a little unusual; it is the name by which the script you are running was called. In the case above, it would have the value ./my_script
. Again, you can see it's value but not change it.
$?
is the "exit status" of the command that just ran. If the command succeeded, then its exit status will be 0
and otherwise it will be a small positive integer. You can compare $?
to zero to see if the previous command succeeded or failed. For example, the following two command lines will run the grep
command and then echo <1>
because the grep
failed and exited with a status of 1
(indicating that it failed).
grep blarg /etc/passwd
echo "<$?>"
The exit status is helpful in writing simple scripts like:
#!/bin/bash
# grep_pw: grep for a pattern in /etc/passwd
grep "$1" /etc/passwd
if [[ $? = 0 ]] ;then
echo "Found it"
exit 0
else
echo "Unable to find the pattern '$1' in /etc/passwd"
exit 1
fi
Put that text into a file named grep_pw
, change it to be executable with chmod 700 grep_pw
, and call it like ./grep_pw nologin
to find the lines in /etc/passwd
which contain the string nologin
.
When I first learned about the shell, I found the following script invaluable for understanding how the shell parses command lines and consequently what command line arguments would be passed to a script.
#!/bin/bash
# echoargs: print all the arguments
counter=1
for a in "$@" ;do
echo "arg $counter=<$a>"
let counter=counter+1
done
Put those contents into a file named echoargs
, change it to be executable with chmod 700 echoargs
and call it like: ./echoargs a "b c" d
.