Shell Scripts
The content of this page is mainly based on MIT Missing Semester.
What we will used for this chapter is bash.
Variables
Assigning to a variable:
foo=bar
echo $foo
bar
Spaces are important. For example, this is not ok:
foo = bar
Quotes
Single quotes and double quotes.
Things in single quotes will be used "as is". However, variables and some other things in double quotes will be evaluated and replaced.
echo "Value is $foo"
echo 'Value is $foo'
Value is bar
Value is $foo
Functions
Here is a simple function definition:
# File: mcd.sh
# Here $1 means the first argument
mcd () {
mkdir -p "$1"
cd "$1"
}
To call it:
source mcd.sh
mcd test
Special Variables
$?
: The return code of the previous command (where 0
usually means everything went fine)
$_
: The last argument of the previous command
!!
: The whole previous command (useful in sudo !!
)
Examples for $?
:
echo "Hello"
echo $?
Hello
0
grep foobar mcd.sh
echo $?
1
true
echo $?
0
false
echo $?
1
Exit Code as Condition
An or operator ||
can be used: if the left hand side fails (return not 0), the right hand side will be executed.
false || echo "Oops fail"
Oops fail
true || echo "Will not be printed"
Note that true
always returns 0
while false
always returns 1.
An and operator &&
can be used: if the left hand side succeeds (return 0), the right hand side will be executed.
An ;
can be used: no matter whether the left hand side succeeds, the right hand side will always be executed.
Passing Command Results
Using the result of a command directly:
foo=$(pwd)
echo "We are in $(pwd)"
Passing the result of a command as a file:
cat <(ls) <(ls ..)
mkdir foo/x bar/y
diff <(ls foo) <(ls bar)
An example script:
# File: example.sh
#!/bin/bash
echo "Starting program at $(date)"
echo "Running program $0 with $# arguments with pid $$"
for file in "$@"; do
grep foobar "$file" > /dev/null 2> /dev/null
if [[ "$?" -ne 0 ]]; then
echo "File $file does not have any foobar, adding one"
echo "# foobar" >> "$file"
fi
done
-
$0
: the name of the script -
$#
: the number of arguments -
$$
: the process id -
$@
: all the arguments -
writing things to
/dev/null
just means disgard them -
>
deals with the standard output -
2>
deals with the standard error -
[ ... ]
ortest
make judgements. -
In bash, use
[[ ... ]]
to replace[]
to prevent errors, although this keyword is not supported bysh
-
-ne
means not equal -
man test
gives you the conditions you can use
The script can be run like:
./example.sh mcd.sh script.py example.sh
Globbing
*
expands to any number (including 0) of characters
?
expands to one character
{}
gives a group and will be expanded for each item in it.
ls *.sh
ls project?
convert image.{png,jpg}
touch foo{,1,2,10}
touch proj{1,2}/src/test/test{1,2,3}.py
mkdir foo bar
touch {foo,bar}/{a..j}
Shebang and Others
You can also write scripts look like shell scripts in some other programming languages. For example python.
For the scripts below, you can run them with ./script.py
directly without specifing python
because of the shebang:
#!/usr/local/bin/python
# Use a specific program
import sys
for arg in reversed(sys.argv[1:]):
print(arg)
#!/usr/bin/env python
# Use the python in the environment
import sys
for arg in reversed(sys.argv[1:]):
print(arg)
The second one is better, because env
will use the python in $PATH
to run the script. For the first one, the path of python cannot be guaranteed.
Using shebang (e.g. #!/bin/bash
) is important and recommended, because the script may use any languages.
A function will be executed in the shell, while a script will be executed in a seperate process.
Shell Tools
To check syntax and see warnings
shellcheck mcd.sh
To check how to use a command, man
and tldr
are both useful. The former is usually longer, while the latter provides some examples and is more convenient to find a correct option.
To find files
find . -name src -type d
find . -path '**/text/*.py' -type f
You can also do something after the files are found:
find . -name "*.tmp" -exec rm {} \;
fd
also searches for files, and it is more simple. You just use fd file_name
You can also use locate
to locate things in the whole file system. However, it only supports searching with file name, and the results may not be up to date.
locate missing-semester
To search for code
Search all foobar in a directory:
grep -R foobar .
You can use -v
to invert the results selected, or -C 5
to see a content of 5 lines.
rg
(ripgrep
) are also useful for this. They support various arguments. Example:
rg -u --files-without-match "^#\!" -t sh
The command above searches for all .sh files that do not have #!
at the beggining of any line.
The idea is: You should know that using suitable tools can help you solve a problem efficiently, while which tool you use is not so important.
Finding shell commands
You can use the arrow key to navigate among the commands that you've used.
Another way to get the command history:
history
history | grep convert
A useful command fzf
history | fzf
To see a structure of directories
tree
broot
nnn
Exercise
(1)
ls -a # list all files
ls -s -h # with size, in human-readable format
ls -c -lt # include last modification time, newest first
(2)
#!/bin/bash
marco() {
export MARCO=$(pwd)
}
polo() {
cd "$MARCO"
}