 davelab
 GitHub

Arguments and Variables

Variables

You can define a variable using =:

my_var="THIS IS MY VARIABLE"

When you want to use a variable, after it has been assigned, you have to prefix the variable name with $ (example: $my_var).

Interpolation

Shell scripts and commands work with lines of text. That means, under-the-hood, all of your variables are strings. And if everything is a string, the we can use features like string concatenation (joining two strings together), and interpolation (printing one string inside of another string).

echo $my_var
#  THIS IS MY VARIABLE

# `interpolate` the current value of `$my_var` into a new string.
my_var="HELLO $my_var"

echo $my_var
#  HELLO THIS IS MY VARIABLE

# `concatenate` two strings just by putting them next to each other.
echo "MESSAGE: "$my_var
#  MESSAGE: HELLO THIS IS MY VARIABLE

Interpolation only works when using double-quoted strings. If you use single-quotes, then there is no ‘magic’ allowed inside of the string - it is purely a literal value.

Capturing Command Output

If you want to run a command and capture it’s output into a string variable, use the $(...) operator.

# capture the STDOUT of the command `ls -al /tmp` into variable `$output_string`
ouput_string="$(ls -al /tmp)"

Be sure to wrap it in double-quotes.

This only captures the STDOUT stream. If you want to also capture the STDERR into the variable, just redirect it to STDERR:

# capture the STDOUT and STDERR of the command `ls -al /tmp` into variable `$output_string`
ouput_string="$(ls -al /tmp 2>&1)"

Script Arguments

The parameters passed to your script are available as sequentially-numbered variables:

Your script doesn’t know that “-x value” means “option ‘x’ with value ‘value’,” it only understands each token as a separate positional argument. Keep your script simple, with positional arguments, or else you will need to use something like “getopts”.

See the later section on “getopt” for details.

If you want all of the arguments at once, the $@ variable will contain them all in an “array” - a string where each value is separated by white-space.

The $0 variable contains the name of the file that was executed. This can be useful when writing help output or examples that need to include the executable name, but you can know in advance where someone will copy or rename the script file.

Default Values

You can read the script argument variables (or any other environment variable from the shell when you executed the script) while supplying a default variable, to be used if the argument (or external variable) does not exist.

ARG1="${1:-default1}"
ARG2="${2:-default2}"

The ${} syntax allows you to expand and operate on variables.

If this script was called with no arguments, then ARG1 would be “default1” and ARG2 would be “default2.”

Finding the Path of the Running Script

Frequently, you will want to write scripts that work on files that we expect to be at a location relative to the script file. For example, scripts that are typically included with a software project source code may need to know exactly how to reach the important files that it needs to work on.

-+ project_root/
 |-- my_script.sh    # <-- this script needs to run over every .py file in the project
 |-- main.py
 |-+ lib/
 | |- server.py
 | |- utilities.py
 |-+ test/
   |- test_server.py
   |- test_utilities.py

If you only use absolute file paths, then you would have to prescribe that everyone working on the project installs the source code at the exact same path on their system. But, if you want to be able to find the other files under the same directory without hard-coding a path, you need to use relative paths.

# absolute path
/hard-coded/path/to/project/lib/server.py

# relative path (from top-level directory of the project folder)
./lib/server.py

So we could write my_script.sh to use relative paths, like ./lib or ./test.

#!/usr/bin/bash
# file: my_script.sh
find ./lib -name '*.py' | wc -l
# relative paths work well, when we run from the directory containing the script file
bash ./my_script.sh

But if we ever try to call the script from a different working directory, then the relative files, like ./lib, are treated as relative the directory we’re running from.

# change to /tmp, away from where the script is located
cd /tmp

# this fails because now it considers `./lib` to mean `/tmp/lib` :(
bash /path/to/project/my_script.sh

To find the working directory, we need to do a little processing of the shell script arguments. We’ll walk through how it works, but you can probably just save this one to your notes or make a snippet to apply it when you need it.

The Easy Way, with ZSH

If you don’t care about “portability” and you are only writing a script for yourself to run, then zsh makes this very easy:

ABS_PATH="${0:a}"

This is still $0, the name of the current script, but we’re applying an expansion operation on the value to get the absolute path. We’ll cover more of the variables and how you can modify them with ${VAR} syntax in the next section.

The “Portable” Way

Here is the “portable” way to do it (will work on most shells):

# get the name and (relative) path of the script (script argument 0)
DIR_NAME="$(dirname -- "$0")"

# get the absolute path of DIR_NAME
ABS_PATH="$(cd $DIR_NAME &>/dev/null && pwd)"

So that is the “portable” way to do it. It is a little strange at first, but not really that bad once you know what it does.

Reading from STDIN Like a UNIX Command

Your scripts can also read from STDIN using the read command. You might not need this very often for simple scripts, and we haven’t covered loops yet, so don’t worry too much about this one right now.

#!/usr/bin/zsh
while read line
do
  echo "input: $line"
done

This would echo every line it reads from STDIN, and prepend “input: “ before each line.


NEXT »