The Only Shell Guide that I Need

·

7 min read

This was written at my very woke moments when I realize I'm probably going to forget about some tiny little details about bash shell if I don't use all of them that much in the future. And also, sometimes, snippets can be handy.

Basics in case I have brain damage

These are some very basic stuffs, in case you really need them.

Shebang!

It's a common thing to add headers (shebang in nature) to shell script to designate the shell you wanna use. You can go with sh or bash or whatever works.

#!/bin/sh
#!/bin/bash

It can have parameters:

#!/bin/sh +x

Even with PHP script, though it's off topic.

#!/usr/bin/env php

Let there be variables

Everything is "string".

You wanna have literals? Gotcha covered.

# No space after variable name or after the equal sign. 
# And yes, this is a string, not an integer or number or anything,
a=123

# which means this is the very same command as:
a="123"

# For strings having spaces or special characters that need to be 
# escaped, you gotta quote them
a="I know, I know\nWhat's next?"

Pass a variable? No problem.

# Yes, you gotta quote them
b="$a"

# Along with other literals
b="a is like: $a"

# In case `a` doesn't have a space around it
b="How many ${a}s do you have again?"

You can even use an output of a command? Coooool!

a="Today is $(date +%Y-%d-%m)"

if

Well, about if control flow, shell has quite a bit of history. So, there are a lot of ways to write conditions, namely [], test command, [[]], let and (()).

Generally, it is written in this manner:

if CONDITION
then
    # Do something
fi

# You can have condition and `then` at the same line
if CONDITION ; then
    # Do something
fi

# You can also have an `else` section
if CONDITION ; then
    # Do A
else
    # Do B
fi

As for how condition is written, that's where the "goodies" are.

[] or test

# How spaces are put is very strict here
# `$a = "123"` is totally different from `$a="123"`
# where `$a="123"` without spaces would be treated
# as a string as a whole instead of testing whether
# $a and 123 are equal
if [ $a = "123" ]
# For bash you can:
if [ $a == "123" ]

# `[]` is pretty much the same as `test`
if test $a = "123"

# if $a and 123 are not equal
if [ $a != "123" ]

# If $a is an empty string
if [ -z $a ]

# If $a is NOT an empty string
if [ -n $a ]
# or
if [ ! -z $a ]

# Numeric comparison notations like "<", ">", ">=", "<=" 
# are not directly supported, you have to do these instead:
# If $a equals to $b
if [ $a -eq $b]
# If $a and $b are not equal
if [ $a -ne $b]
# If $a is greater than $b
if [ $a -gt $b]
# If $a is greater or equal than $b
if [ $a -ge $b]
# If $a is less than $b
if [ $a -lt $b]
# If $a is less or equal than $b
if [ $a -le $b]

# For logic operators, you have to use these instead:

# If $a equals 1 and $b equals 2
if [ $a = 1 -a $b = 2 ] # Note this does NOT short circuit
# or
if [ $a = 1 ] && [ $b = 2 ] # Note this does short circuit

# If $a equals 1 or $b equals 2
if [ $a = 1 -o $b = 2 ] # Again, this does NOT short circuit
# or
if [ $a = 1 ] || [ $b = 2 ] # This does short circuit

# If `$dir` is a directory path that exists
if [ -d "$dir"]

# If `$file_path` is not a file path that exists
if [ ! -f "$file_path"]

[[]] is not very universal, you could probably only use it with Bash or ksh. It was introduced into Bash at the version 2.02. But it is much more capable than [] in terms of expressiveness.

# You can use wildcards here. But still, spaces are very
# important
if [[ $a = 12* ]] # will match `123`, `12f2`, `12dsa`...
if [[ $a = 12? ]] # will match `123`, `12f`, `12w`...

# If you mean star literally, you should quote it so that
# it won't be treated as a wildcard 
if [[ $a = "12*" ]] # will only match `12*`

# You can compare using normal math notations here
if [[ $a > $b ]]

# You can use logic operators inside here
if [[ $a = 1 && $b = 2 ]] # This does short circuit evaluation
# or
if [[ $a = 1 ]] && [[ $a = 2 ]] # This also does short circuit evaluation

(()) is basically doing arithmetic calculations, but its result can be used as a logic value for if control flow.

# In `(())`, you can omit dollar sign for variables
# and also, spaces in the `(())` are not very strict
if ((a==10))

# Note that a single equal sign `=` means assignment here
if ((a=1)) # This will always be true and $a will become 1

# You can do these:
if ((a>6 && b<10))
if ((a/3==4))
# Pretty impressive, no?

while loops

If you understand how conditions are written for if statement, then while should be no problem for you. There is also another thing called until loops, which work very much like while loops, the only difference being until loops only run its loops when CONDITION are NOT met.

while CONDITION
do
    # Repeatedly do something
done

# A very simple example
while ((i <= 100))
do
    ((sum += i))
    ((i++))
done

# Same thing but using until
until ((i > 100))
do
    ((sum += i))
    ((i++))
done

# You can do `continue` and `break` 
until ((i > 100))
do
    ((sum += i))
    ((i++))
    if ((i==40)) ; then
        break
    else
        continue
    fi
done

for loops

for loops have two styles, one like C/C++, the other like python. You can also do break and continue by the way.

C style for loop

for ((i=1; i<=100; i++))
do
    ((sum += i))
done

Python style for loop

# List values
for n in 1 2 3 4 5 6
do
    echo $n
     ((sum+=n))
done

# Can be strings. (Numbers are also strings actually)
for str in "a" "b" "c"
do
    echo $str
done

# Can be a range
for n in {1..100}
do
    ((sum+=n))
done

# Or range of characters (in the order of ASCII codes)
for c in {A..z}
do
    echo $c # This will include []^_`
done

# Can also use the result of a commmand
for filename in $(ls *.sh)
do
    echo $filename
done

Unix special variable list

VariableDescription
$0The filename of the current script.
$nThese variables correspond to the arguments with which a script was invoked. Here n is a positive decimal number corresponding to the position of an argument (the first argument is $1, the second argument is $2, and so on).
$#The number of arguments supplied to a script.
$*All the arguments, all double quoted. If a script receives two arguments, $* is equivalent to $1 $2.
$@All the arguments, all individually double quoted. If a script receives two arguments, $@ is equivalent to $1 $2.
$?The exit status of the last command executed.
$$The process number of the current shell. For shell scripts, this is the process ID under which they are executing.
$!The process number of the last background command.

Snippets

Get script path

shell_dir=$(dirname "$0")

Delete a directory if exits

if [ -d "$dir" ] ; then
  rm -rf "$dir"
fi

Create a directory if it doesn't exist

if [ ! -d "$dir" ] ; then
  mkdir -p "$dir"
fi