Skip to content

Instantly share code, notes, and snippets.

@semick-dev
Last active May 22, 2024 23:57
Show Gist options
  • Save semick-dev/c678a48ec216380fbc0893979d9d9240 to your computer and use it in GitHub Desktop.
Save semick-dev/c678a48ec216380fbc0893979d9d9240 to your computer and use it in GitHub Desktop.
Bash QuickRef

Bash QuickRef

Shell manipulation

Escaping the . in . including

If you want to be absolutely certain that your script is being dot-included, escape the dot.

# note the dot is escaped, and has a space to where the invoked script actually exists
\. "$NVM_DIR/nvm.sh"

Difference between . and SOURCE and ./

source script.sh exec script.sh ./path/to/script.sh
Invokes the targeted script in the current shell. . and source are usually aliases of each other used interchangably. Terminates the current shell and then execute the target script in place. Invokes the target script in a new child shell. ./ denotes the current directory.

This writeup by an enterprising stack overflow user is really the best explanation I have found online.

execute permissions

To invoke a script via exec or . requires execute permissions on the file. However, if you source it you do not require execute permissions on the file!

prompt variables and other Prompt Modification

To modify the appearance of the prompt message that appears on every line, two actions must be completed.

C:/repo [N/A]|>
       ^^^^^^^^ <-- custom prompt
  1. Override the PROMPT_COMMAND variable to set a custom prompt function.
  2. Within the custom prompt function, ensure you set PS1 variable. This is the environment variable containing the prompt message for the current line.
prompt_func() {
  PS1="\w \e"
}

PROMPT_COMMAND=prompt_func

To see what is currently defined in your .bashrc/.bash_profile use grep 'PS1' $HOME/.bash{rc,_profile}. There are a few different prompt variables:

Name Purpose
PS1 Default interactive prompt (this is the variable most often customized).
PS2 Continuation interactive prompt (when a long command is broken up with \ at the end of the line) default=>.
PS3 Prompt used by select loop inside a shell script.
PS4 Prompt used when a shell script is executed in debug mode (set -x will turn this on) default =++.
PROMPT_COMMAND If this variable is set and has non-null value, it will be executed (as a function) just before the PS1 variable output.

Special prompt variable characters:

Character Substitution Value
\d The date, in "Weekday Month Date" format (e.g., "Tue May 26").
\h The hostname, up to the first . (e.g. deckard)
\H The hostname. (e.g. deckard.SS64.com)
\j The number of jobs currently managed by the shell.
\l The basename of the shell's terminal device name.
\s The name of the shell, the basename of $0 (the portion following the final slash).
\t The time, in 24-hour HH:MM:SS format.
\T The time, in 12-hour HH:MM:SS format.
\@ The time, in 12-hour am/pm format.
\u The username of the current user.
\v The version of Bash (e.g., 2.00)
\V The release of Bash, version + patchlevel (e.g., 2.00.0)
\w The current working directory.
\W The basename of $PWD.
\! The history number of this command.
\# The command number of this command.
\$ If you are not root, inserts a "$"; if you are root, you get a "#" (root uid = 0)
\nnn The character whose ASCII code is the octal value nnn.
\n A newline.
\r A carriage return.
\e An escape character (typically a color code).
\a A bell character.
\\ A backslash.
\[ Begin a sequence of non-printing characters. (like color escape sequences). This allows bash to calculate word wrapping correctly.
\] End a sequence of non-printing characters.

It is recommended to use ' instead of " when exporting PS variables, as it makes the prompt a tiny bit faster to evaluate.

Variables

A variable must:

  • Start with a letter [a-zA-Z].
  • Can include a digit [0-9] as long as the function name does not begin with one.
  • Can include a _, though a variable can be started with an _.
a=(ls -l)
a=$ENV_SETTING
a="a string"
read a
echo $a

# or
echo "${a}"

A note about assigning variables. Don't have any space on EITHER side of the = sign. It's much easier to read, but you will have really strange issues.

A note about referencing variables in strings

VARIABLE1="hello there good sir"

echo '$VARIABLE1' # -> ''. Because bash doesn't expand variables when surrounded by single quotes
echo "$VARIABLE1" # -> "hello there good sir". Because bash DOES expand variables when surrounded by double quotes.

Default values for variables

foo=
echo ${foo:-"variable if unset"}
echo $foo # -> "variable if unset"
foo=3
echo ${foo:-"variable if unset"}
echo $foo # -> "3"

Parameter expansion

Operation Result
${parameter:-word} Returns the value of word if parameter is unset or empty.
${parameter:=word} Returns the value of word if parameter is unset or empty. Also assigns the value of word to parameter.
${parameter:?word} Exits the script with an error described in word if parameter is unset or empty.

Variable scoping

Note that the first time a variable is set and used, it is visible outside of the scope where it is defined.

func() {
    global_var=42
}
echo "$global_var" # -> empty

func

echo "$global_var" # -> 42

To bind a variable name only to the current scope or code block, preface the declaration with local.

func() {
    local local_var=42
}
echo "$local_var" # -> empty

func

echo "$local_var" # -> empty, local_var remained constrained to function block.

Arguments

Arguments to the script are accessed through the $N variable. Note that script arguments are not immediately accessible to function scopes. One must pass a script argument on to the function to make it available.

# arg_example.sh

echo $1 $2

function helloworld() {
    echo $1 $2
}

helloworld $1 $2
helloworld $2 $1
helloworld chicken butt

Called as below:

semick@terra:~/repo/locker/armory/interface/sh-cli$ ./arg_example.sh hello world
Original hello world
function chicken world
function world butt

Expression defaults

Check the parameter expansion section below.

Functions

function myfuncname() {
    local b=(ls -l)
    echo "hello world!"
    echo b
}

myfuncname()
function myfunctioname() {
    local b=${1:-"hahaha!"}
    echo $b
}

myfunctioname "moop"

Control flow with if

if ["$x" -eq 5]; then
    echo "x equals 5"
else
    echo "nope"
fi

if [ bool ]; then
    # <expr>
elif [ bool ]; then
    # <expr>
else
    # <expr>
fi

Single line if statements

Separate statements with ;.

# simple form
if [ -d $STAGING_DIRECTORY ]; then echo "exists!"; else echo "returning false!"; fi

# advanced form
if [[ -d $STAGING_DIRECTORY ]]; then echo "exists!"; else echo "returning false!"; fi

# test form
if test -d $STAGING_DIRECTORY; then echo "exists!"; else echo "returning false!"; fi

Advanced if statements

If possible, prefer the new test EG [[ EXPRESSION ]] syntax. It gives you access to better operators like REGEX!

if [[ "$x" =~ ^-?[0-9]+$ ]]; then
    # <expr>
fi

You get access to =~ or regex equality when you use the [[ instead of a single [ for expressions.

[] and [[]] (alternative form (())) use && and || for the or logical operator. An alternative form is to place test in front of the ;.

if test $EXPRESSION; then
    # <expr>
fi

Combine logical operators in the following way:

Operation test [[ ]] and (( ))
AND -a &&
OR -o ||
NOT ! !

Expressions

File Expressions

Expression is true if
file1 -ef file file1 and file2 have the same inode numbers (the two filenames refer to the same file by hard linking).
file1 -nt file2 file1 is newer than file2.
file1 -ot file2 file1 is older than file2.
-b file file exists and is a block-special (device) file.
-c file file exists and is a character-special (device) file.
-d file file exists and is a directory.
-e file file exists.
-f file file exists and is a regular file.
-g file file exists and is a set-group-ID.
-G file file exists and is owned by the effective group ID.
-k file file exists and has its "sticky bit" set.
-L file file exists and is a symbolic link.
-O file file exists and is owned by the effective user ID.
-p file file exists and is a named pipe.
-r file file exists and is readable (has readable permission for the effective user).
-s file file exists and has a length greater than 0.
-S file file exists and is a network socket.
-t fd
-u file file exists and is setuid.
-w file file exists and is writable (has write permission for the effective user).
-x file `file exists and is executable (has execute/search permission for the effective user).
Usage Meaning
effective group ID The group of user invoking the current session.
effective user ID The user invoking the current session.
file descriptor Is a process-unique identifier (handle) for a file or other i/o resource, such as a pipe or a network socket.
setuid or setguid set user identity or set group identity are access flags that allow a user to invoke an executable with the file-system permissions of that executable and not the current user!

String Expressions

Expression is true if
string string is not null
-n string The length of string is greater than 0.
-z string The length of string is zero.
string1 = string2
string1 == string2
string1 and string2 are equal. Double = form preferred.
string != string2 string1 and string2 are not equal.
string1 > string2 string1 sorts after string2
string1 < string2 string1 sorts before string2

Normally to check if a string is in another string, you'll just use the regex operator.

if [[ $SEARCHSTRING =~ .*"$BIGSTRING".* ]]; then
    echo "It's there!"
fi

Integer Expressions

To compare values as strings, use the following expressions.

Expression is true if
integer1 -eq integer2 integer1 is equal to integer2.
integer1 -ne integer2 integer1 is not equal to integer2.
integer1 -le integer2 integer1 is less than or equal to integer2.
integer1 -lt integer2 integer1 is less than integer2.
integer1 -ge integer2 integer1 is greater than or equal to integer2.
integer1 -gt integer2 integer1 is greater than integer2.

Surround purely integer expressions in (()).

INT=5
if ((INT ==0)); then
    echo "INT is zero."
fi

User input

echo -n "enter an integer"
read myinteger
echo $myinteger

read -p "Enter a username > " user_name
echo $user_name

Looping

count=1
while [[ "$count" -le 5 ]]; do
    echo $count
    count=$((count + 1))
done
while true ; do
    read x
    if [[ $x == "a"]]; then
        echo "$x == 10"
        continue
    else
        break
    fi
done
count=1

until [[ "$count" -gt 5 ]]; do
    echo "$count"
    count=$((count + 1))
done

So you can read from a file by redirecting input to the loop using the < operator trailing your loop-end done.

while read distro version release; do
    echo $distro
    echo $version
    echo $release
done < distros.txt

But another good way is to pipe in the input. That also follows very well.

sort -k 1,1 -l 2n distros.txt | while read distro version release; do
    echo $distro
    echo $version
    echo $release
done

Exit codes

Use exit 1 to exit with a general failure code.

Exit Code Number Meaning
1 Catchall for general errors
2 Misuse of shell builtins (according to Bash documentation)
126 Command invoked cannot execute
127 "command not found"
128 Invalid argument to exit. (this is returned when > 255 is attempted to be returned)
128+n Fatal error signal "n"
130 Script terminated by Control-C
> 255 Exit status out of range

Case statement

case "$REPLY" in
    [[:alpha:]]) echo "is a single alphabetic character." ;;
    [ABC][0-9])  echo "Is A,B, or C followed by a digit." ;;
    ???)         echo "Is 3 characters long." ;;
    *.txt|*.json echo "Ends in *.txt or *.json" ;;
    *)           echo "Something else"
esac

Other [[]] operations.

Operation matches if character
[[:upper:]] is upper case.
[[:lower:]] is lower case.
[[:alpha:]] is alphabetic.
[[:digit:]] is a digit.
[[:graph:]] is a visible character.
[[:punct:]] is a punctuation symbol.
[[:space:]] is a whitespace character.
[[:xdigit:]] is a hexadecimal digit.

Cascading case statements

If you want to evaluation to continue past a match, simply add a trailing & to a cases terminating ;;.

case "$REPLY" in
    [[:alpha:]]) echo "is a single alphabetic character." ;;&
    [ABC][0-9])  echo "Is A,B, or C followed by a digit." ;;&
    ???)         echo "Is 3 characters long." ;;&
    *.txt|*.json echo "Ends in *.txt or *.json" ;;&
    *)           echo "Something else"
esac

# $REPLY is "333"
# will print "is 3 characters long" and "Something else" because the case cascades though due to the `&`.

Arrays

Single dimension only.

# created automatically when accessed

a[1]=foo
echo ${a[1]}

An array can also be created bare by:

# -a is what makes `a` an array variable
declare -a a

You can assign in two ways.

name[subscript]=value

name=(index0 index1 index2 index3 ...)

days=(Sun Mon Tue Wed Thu Fri Sat)

days=([0]=Sun [1]=Mon [2]=Tue [3]Wed [4]Thu [5]Fri [6]Sat)

Ok, so how can we actually use the above? How do we iterate?

#!/usr/bin/bash

usage() {
    echo "usage: ${0##*/} directory" >&2
}

if [[ ! -d "$1" ]]; then
    usage
    exit 1
fi

for i in {0..23}; do hours[i]=0; done

for i in $(stat -c %y "$1" /* | cut -c 12-13); do
    j="{i#0}"
    ((++hours[j]))
    ((++count))
done

echo -e "Hours\tFiles\tHour\tFiles"
echo -e "-----\t-----\t----\t-----"
for i in {0..11}; do
    j=$((i + 12))
    printf "%02d\t%d\t02d\t%d\n" \
            "$i" \
            "${hours[i]}" \
            "$j" \
            "${hours[j]}"
done
printf "\n Total files = %d\n" $count

Associative Arrays

You can declare an array which can by accessed by string value.

declare -A fruits
fruits[india]="Banana"
fruits[california]="Orange"
fruits[washington]="Apple"

echo ${fruits[washington]}

Common Usage

One-liner for invoking command on successful execution

if [ $(ls | wc -l) == 0 ]; then echo 1; else echo 0; fi

This can be terser though, if you don't need the else side.

[ <boolean expression> ] && echo "successful if"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment