This page looks best with JavaScript enabled

Bash Scripting

 ·   ·  โ˜• 10 min read

    Getting started

    Example

    1
    2
    3
    4
    
    #!/usr/bin/env bash
    
    NAME="John"
    echo "Hello $NAME!"
    

    Variables

    1
    2
    3
    4
    
    NAME="John"
    echo $NAME
    echo "$NAME"
    echo "${NAME}!"
    

    String quotes

    1
    2
    3
    
    NAME="John"
    echo "Hi $NAME"  #=> Hi John
    echo 'Hi $NAME'  #=> Hi $NAME
    

    Shell execution

    1
    2
    3
    
    echo "I'm in $(pwd)"
    echo "I'm in `pwd`"
    # Same
    

    See Command substitution

    Conditional execution

    1
    2
    
    git commit && git push
    git commit || echo "Commit failed"
    

    Functions

    1
    2
    3
    4
    5
    
    get_name() {
      echo "John"
    }
    
    echo "You are $(get_name)"
    

    See: Functions

    Conditionals

    1
    2
    3
    4
    5
    
    if [[ -z "$string" ]]; then
      echo "String is empty"
    elif [[ -n "$string" ]]; then
      echo "String is not empty"
    fi
    

    See: Conditionals

    Strict mode

    1
    2
    
    set -euo pipefail
    IFS=$'\n\t'
    

    See: Unofficial bash strict mode

    Brace expansion

    1
    
    echo {A,B}.js
    
    1
    2
    3
    
    {A,B}				#> Same as A B
    {A,B}.js			#> Same as A.js B.js
    {1..5}				#> Same as 1 2 3 4 5
    

    See: Brace expansion

    Parameter expansions

    Basics

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    name="John"
    echo ${name}
    echo ${name/J/j}    #=> "john" (substitution)
    echo ${name:0:2}    #=> "Jo" (slicing)
    echo ${name::2}     #=> "Jo" (slicing)
    echo ${name::-1}    #=> "Joh" (slicing)
    echo ${name:(-1)}   #=> "n" (slicing from right)
    echo ${name:(-2):1} #=> "h" (slicing from right)
    echo ${food:-Cake}  #=> $food or "Cake"
    
    1
    2
    
    length=2
    echo ${name:0:length}  #=> "Jo"
    

    See: Parameter expansion

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    STR="/path/to/foo.cpp"
    echo ${STR%.cpp}    # /path/to/foo
    echo ${STR%.cpp}.o  # /path/to/foo.o
    
    echo ${STR##*.}     # cpp (extension)
    echo ${STR##*/}     # foo.cpp (basepath)
    
    echo ${STR#*/}      # path/to/foo.cpp
    echo ${STR##*/}     # foo.cpp
    
    echo ${STR/foo/bar} # /path/to/bar.cpp
    
    1
    2
    3
    
    STR="Hello world"
    echo ${STR:6:5}   # "world"
    echo ${STR:-5:5}  # "world"
    
    1
    2
    3
    
    SRC="/path/to/foo.cpp"
    BASE=${SRC##*/}   #=> "foo.cpp" (basepath)
    DIR=${SRC%$BASE}  #=> "/path/to/" (dirpath)
    

    Substitution

    CodeDescription
    ${FOO%suffix}Remove suffix
    ${FOO#prefix}Remove prefix
    ${FOO%%suffix}Remove long suffix
    ${FOO##prefix}Remove long prefix
    ${FOO/from/to}Replace first match
    ${FOO//from/to}Replace all
    ${FOO/%from/to}Replace suffix
    ${FOO/#from/to}Replace prefix

    Comments

    1
    
    # Single line comment
    
    1
    2
    3
    4
    5
    
    : '
    This is a
    multi line
    comment
    '
    

    Substrings

    1
    2
    
    ${FOO:0:3}			#=> Substring (position, length)
    ${FOO:-3:3}			#=> Substring from the right
    

    Length

    1
    
    ${#FOO}				#=> Length of $FOO
    

    Manipulation

    1
    2
    3
    4
    5
    6
    7
    
    STR="HELLO WORLD!"
    echo ${STR,}   #=> "hELLO WORLD!" (lowercase 1st letter)
    echo ${STR,,}  #=> "hello world!" (all lowercase)
    
    STR="hello world!"
    echo ${STR^}   #=> "Hello world!" (uppercase 1st letter)
    echo ${STR^^}  #=> "HELLO WORLD!" (all uppercase)
    

    Default values

    1
    2
    3
    4
    
    ${FOO:-val}				#=> $FOO or val if not set
    ${FOO:=val}				#=> $FOO to val if not set
    ${FOO:+val}				#=> val if $FOO is set
    ${FOO:?message}				#=> Show error message and exit if $FOO is not set
    

    The : is optional (eg, ${FOO=word} works)

    Loops

    Basic for loop

    1
    2
    3
    
    for i in /etc/rc.*; do
      echo $i
    done
    

    C-like for loop

    1
    2
    3
    
    for ((i = 0 ; i < 100 ; i++)); do
      echo $i
    done
    

    Ranges

    1
    2
    3
    
    for i in {1..5}; do
        echo "Welcome $i"
    done
    

    With step size

    1
    2
    3
    
    for i in {5..50..5}; do
        echo "Welcome $i"
    done
    

    Reading lines

    1
    2
    3
    
    < file.txt | while read line; do
      echo $line
    done
    

    Forever

    1
    2
    3
    
    while true; do
      ยทยทยท
    done
    

    Functions

    Defining functions

    1
    2
    3
    
    myfunc() {
        echo "hello $1"
    }
    
    1
    2
    3
    4
    
    # Same as above (alternate syntax)
    function myfunc() {
        echo "hello $1"
    }
    
    1
    
    myfunc "John"
    

    Returning values

    1
    2
    3
    4
    
    myfunc() {
        local myresult='some value'
        echo $myresult
    }
    
    1
    
    result="$(myfunc)"
    

    Raising errors

    1
    2
    3
    
    myfunc() {
      return 1
    }
    
    1
    2
    3
    4
    5
    
    if myfunc; then
      echo "success"
    else
      echo "failure"
    fi
    

    Arguments

    ExpressionDescription
    $#Number of arguments
    $*All arguments
    $@All arguments, starting from first
    $1First argument

    See Special parameters.

    Conditionals

    Conditions

    Note that [[ is actually a command/program that returns either 0 (true) or 1 (false). Any program that obeys the same logic (like all base utils, such as grep(1) or ping(1)) can be used as condition, see examples.

    ConditionDescription
    [[ -z STRING ]]Empty string
    [[ -n STRING ]]Not empty string
    [[ STRING == STRING ]]Equal
    [[ STRING != STRING ]]Not Equal
    [[ NUM -eq NUM ]]Equal
    [[ NUM -ne NUM ]]Not equal
    [[ NUM -lt NUM ]]Less than
    [[ NUM -le NUM ]]Less than or equal
    [[ NUM -gt NUM ]]Greater than
    [[ NUM -ge NUM ]]Greater than or equal
    [[ STRING =~ STRING ]]Regexp
    (( NUM < NUM ))Numeric conditions
    ConditionDescription
    [[ -o noclobber ]]If OPTIONNAME is enabled
    [[ ! EXPR ]]Not
    [[ X ]] && [[ Y ]]And
    [[ X ]] || [[ Y ]]Or

    File conditions

    ConditionDescription
    [[ -e FILE ]]Exists
    [[ -r FILE ]]Readable
    [[ -h FILE ]]Symlink
    [[ -d FILE ]]Directory
    [[ -w FILE ]]Writable
    [[ -s FILE ]]Size is > 0 bytes
    [[ -f FILE ]]File
    [[ -x FILE ]]Executable
    [[ FILE1 -nt FILE2 ]]1 is more recent than 2
    [[ FILE1 -ot FILE2 ]]2 is more recent than 1
    [[ FILE1 -ef FILE2 ]]Same files

    Example

    1
    2
    3
    4
    5
    6
    
    # String
    if [[ -z "$string" ]]; then
      echo "String is empty"
    elif [[ -n "$string" ]]; then
      echo "String is not empty"
    fi
    
    1
    2
    3
    4
    
    # Combinations
    if [[ X ]] && [[ Y ]]; then
      ...
    fi
    
    1
    2
    
    # Equal
    if [[ "$A" == "$B" ]]
    
    1
    2
    
    # Regex
    if [[ "A" =~ "." ]]
    
    1
    2
    3
    
    if (( $a < $b )); then
       echo "$a is smaller than $b"
    fi
    
    1
    2
    3
    
    if [[ -e "file.txt" ]]; then
      echo "file exists"
    fi
    

    Arrays

    Defining arrays

    1
    
    Fruits=('Apple' 'Banana' 'Orange')
    
    1
    2
    3
    
    Fruits[0]="Apple"
    Fruits[1]="Banana"
    Fruits[2]="Orange"
    

    Working with arrays

    1
    2
    3
    4
    5
    6
    
    echo ${Fruits[0]}           # Element #0
    echo ${Fruits[@]}           # All elements, space-separated
    echo ${#Fruits[@]}          # Number of elements
    echo ${#Fruits}             # String length of the 1st element
    echo ${#Fruits[3]}          # String length of the Nth element
    echo ${Fruits[@]:3:2}       # Range (from position 3, length 2)
    

    Operations

    1
    2
    3
    4
    5
    6
    7
    
    Fruits=("${Fruits[@]}" "Watermelon")    # Push
    Fruits+=('Watermelon')                  # Also Push
    Fruits=( ${Fruits[@]/Ap*/} )            # Remove by regex match
    unset Fruits[2]                         # Remove one item
    Fruits=("${Fruits[@]}")                 # Duplicate
    Fruits=("${Fruits[@]}" "${Veggies[@]}") # Concatenate
    lines=(`cat "logfile"`)                 # Read from file
    

    Iteration

    1
    2
    3
    
    for i in "${arrayName[@]}"; do
      echo $i
    done
    

    Dictionaries

    Defining

    1
    
    declare -A sounds
    
    1
    2
    3
    4
    
    sounds[dog]="bark"
    sounds[cow]="moo"
    sounds[bird]="tweet"
    sounds[wolf]="howl"
    

    Declares sound as a Dictionary object (aka associative array).

    Working with dictionaries

    1
    2
    3
    4
    5
    
    echo ${sounds[dog]} # Dog's sound
    echo ${sounds[@]}   # All values
    echo ${!sounds[@]}  # All keys
    echo ${#sounds[@]}  # Number of elements
    unset sounds[dog]   # Delete dog
    

    Iteration

    Iterate over values

    1
    2
    3
    
    for val in "${sounds[@]}"; do
      echo $val
    done
    

    Iterate over keys

    1
    2
    3
    
    for key in "${!sounds[@]}"; do
      echo $key
    done
    

    Options

    Options

    1
    2
    3
    4
    
    set -o noclobber  # Avoid overlay files (echo "hi" > foo)
    set -o errexit    # Used to exit upon error, avoiding cascading errors
    set -o pipefail   # Unveils hidden failures
    set -o nounset    # Exposes unset variables
    

    Glob options

    1
    2
    3
    4
    5
    
    set -o nullglob    # Non-matching globs are removed  ('*.foo' => '')
    set -o failglob    # Non-matching globs throw errors
    set -o nocaseglob  # Case insensitive globs
    set -o globdots    # Wildcards match dotfiles ("*.sh" => ".foo.sh")
    set -o globstar    # Allow ** for recursive matches ('lib/**/*.rb' => 'lib/a/b/c.rb')
    

    Set GLOBIGNORE as a colon-separated list of patterns to be removed from glob
    matches.

    History

    Commands

    1
    2
    
    history                 #=> Show history
    shopt -s histverify     #=> Don't execute expanded result immediately
    

    Expansions

    1
    2
    3
    4
    5
    
    !$                      #=> Expand last parameter of most recent command
    !*                      #=> Expand all parameters of most recent command
    !-n                     #=> Expand nth most recent command
    !n                      #=> Expand n th command in history
    !<command>              #=> Expand most recent invocation of command <command>
    

    Operations

    1
    2
    3
    4
    5
    
    !!                        #=> Execute last command again
    !!:s/<FROM>/<TO>/         #=> Replace first occurrence of <FROM> to <TO> in most recent command
    !!:gs/<FROM>/<TO>/        #=> Replace all occurrences of <FROM> to <TO> in most recent command
    !$:t                      #=> Expand only basename from last parameter of most recent command
    !$:h                      #=> Expand only directory from last parameter of most recent command
    

    !! and !$ can be replaced with any valid expansion.

    Slices

    1
    2
    3
    4
    5
    
    !!:n           #=> Expand only n th token from most recent command (command is 0 ; first argument is 1)
    !^             #=> Expand first argument from most recent command
    !$             #=> Expand last token from most recent command
    !!:n-m         #=> Expand range of tokens from most recent command
    !!:n-$         #=> Expand n th token to last from most recent command
    

    !! can be replaced with any valid expansion i.e. !cat, !-2, !42, etc.

    Miscellaneous

    Numeric calculations

    1
    
    $((a + 200))      # Add 200 to $a
    
    1
    
    $((RANDOM%=200))  # Random number 0..200
    

    Subshells

    1
    2
    
    (cd somedir; echo "I'm now in $PWD")
    pwd # still in first directory
    

    Redirection

    1
    2
    3
    4
    5
    6
    
    python hello.py > output.txt   # stdout to (file)
    python hello.py >> output.txt  # stdout to (file), append
    python hello.py 2> error.log   # stderr to (file)
    python hello.py 2>&1           # stderr to stdout
    python hello.py 2>/dev/null    # stderr to (null)
    python hello.py &>/dev/null    # stdout and stderr to (null)
    
    1
    
    python hello.py < foo.txt      # feed foo.txt to stdin for python
    

    Inspecting commands

    1
    2
    
    command -V cd
    #=> "cd is a function/alias/whatever"
    

    Trap errors

    1
    
    trap 'echo Error at about $LINENO' ERR
    

    or

    1
    2
    3
    4
    5
    6
    
    traperr() {
      echo "ERROR: ${BASH_SOURCE[1]} at about ${BASH_LINENO[0]}"
    }
    
    set -o errtrace
    trap traperr ERR
    

    Case/switch

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    case "$1" in
      start | up)
        vagrant up
        ;;
    
      *)
        echo "Usage: $0 {start|stop|ssh}"
        ;;
    esac
    

    Source relative

    1
    
    source "${0%/*}/../share/foo.sh"
    

    printf

    1
    2
    3
    4
    5
    6
    7
    8
    
    printf "Hello %s, I'm %s" Sven Olga
    #=> "Hello Sven, I'm Olga
    
    printf "1 + 1 = %d" 2
    #=> "1 + 1 = 2"
    
    printf "This is how you print a float: %f" 2
    #=> "This is how you print a float: 2.000000"
    

    Directory of script

    1
    
    DIR="${0%/*}"
    

    Getting options

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do case $1 in
      -V | --version )
        echo $version
        exit
        ;;
      -s | --string )
        shift; string=$1
        ;;
      -f | --flag )
        flag=1
        ;;
    esac; shift; done
    if [[ "$1" == '--' ]]; then shift; fi
    

    Heredoc

    1
    2
    3
    
    cat <<END
    hello world
    END
    

    Reading input

    1
    2
    3
    
    echo -n "Proceed? [y/n]: "
    read ans
    echo $ans
    
    1
    
    read -n 1 ans    # Just one character
    

    Special variables

    1
    2
    3
    4
    
    $?          #=> Exit status of last task
    $!          #=> PID of last background task
    $$          #=> PID of shell
    $0          #=> Filename of the shell script
    

    See Special parameters.

    Go to previous directory

    1
    2
    3
    4
    5
    
    pwd # /home/user/foo
    cd bar/
    pwd # /home/user/foo/bar
    cd -
    pwd # /home/user/foo
    

    Check for command’s result

    1
    2
    3
    
    if ping -c 1 google.com; then
      echo "It appears you have a working internet connection"
    fi
    

    Grep check

    1
    2
    3
    
    if grep -q 'foo' ~/.bash_history; then
      echo "You appear to have typed 'foo' in the past"
    fi
    

    Also see

    Share on

    Iqbal
    WRITTEN BY
    Iqbal
    I am just a man, not superhuman!