Skip to content

Bash (scripting, not shell)

Created: 2016-03-25 15:31:50 -0700 Modified: 2022-07-04 15:09:22 -0700

  • Good Gist here with some general guidelines
  • ShellCheck is a script analysis tool for shell scripts
  • Safe scripting (e.g. by exiting on any error): reference
    • For any of the following, just put a line at the top of the script, e.g. “set -Eeuo pipefail”.
      • ↑ In fact, that command is a good start for most Bash scripts to make it work like how you’re used to with most programming languages.
    • set -e: exit immediately when a command fails
    • set -o pipefail: sets exit code of a pipeline (piped commands) to the rightmost failing command
    • set -u: treat unset variables as an error and exit immediately
    • set -x: print every command before executing it. It’s like Batch’s “@echo on”
  • .bash_profile vs. .bashrc (reference, reference for zsh): in short: bash_profile is for commands that should only run once (like setting your $PATH), bashrc is good for commands that should run every time you start a new shell.
  • Interactive vs. login shell (reference): in short: a login shell is when you logged into the system through a shell, e.g. SSHing into a machine. An interactive shell is like when you start a new terminal on macOS.
  • Check if a command was successful (reference)
some_command
if [ $? -eq 0 ]; then
echo OK
else
echo FAIL
fi

You can also do this

some_command
retval=$?
do_something $retval
if [ $retval -ne 0 ]; then
echo "Return code was not zero but $retval"
fi
  • Return
    • ”return” isn’t required from all functions
  • Arguments
    • Reading script-level arguments is the same as reading function-level arguments: 1,1, 2, etc.
    • I find it nice to name arguments in functions anyway:
add_two_numbers() {
OPERAND_ONE=$1
OPERAND_TWO=$2
(( result = OPERAND_ONE + OPERAND_TWO ))
}
add_two_numbers 5 6
echo Result is: $result
  • Comparing arguments is easy
if [[ "$1" != "deploy" && "$1" != "delete" ]]; then
echo "You need to specify either 'deploy' or 'delete'"
exit 1
fi
  • Checking if an argument is empty (reference)
if [ -z "$1" ] # Note: to check if it's not empty, just do if [ ! -z "$1" ]
then
echo "No argument supplied"
fi
  • To pass in an empty argument, specify open and closing quotation marks like this:

    • ./foo.sh "" second_arg "" fourth_arg
  • Using temporary buffers

Suppose you have sounds.json like this:

{
"frog": "REPLACE_ME",
"dog": "woof"
}

You can modify REPLACE_ME without having to make a temporary file by using a temporary buffer:

cat <(cat sounds.json | sed s/REPLACE_ME/ribbit/g)

↑ [09:24] Pneumatus: that sed command in the temporary buffers section, you know there’s an ‘-i’ arg to sed that lets you do in-place replacement? :)

[09:25] Pneumatus: sed -i ‘s/REPLACE_ME/ribbit/g’ sounds.json

[09:25] Pneumatus: or maybe the other way round

  • If/elif/end
# This is just a simple example that does nothing helpful:
if [[ "$arg" == "..." ]]; then
# Go up 2 directories
cd ../..
elif [[ "$arg" == "...." ]]; then
# Go up 3 directories
cd ../../..
else
# Don't do anything
fi
  • Set error handling: set -Eeuo pipefail
    • This was mentioned earlier, but it makes your environment more like a programming environment.
  • Write to stderr and then exit (reference)
die () {
echo >&2 "$@"
exit 1
}

This is useful for doing something like:

Section titled This is useful for doing something like:

echo 1grepEq[09]+1 | grep -E -q '^[0-9]+’ || die “Numeric argument required, $1 provided”

You’re supposed to use $():

cur_date = $(date)

It used to be that you’d use backticks, e.g.

cur_date=date

(do not use this since it’s deprecated)

Simple example:

foo=5
bar=6
(( baz = foo + bar ))
echo $baz # This shows 11

For floating-point calculations, use bc.

()vs.() vs. {}

Section titled () vs. {}

[14:22] woodworker_: so (something)willrunthecommandandreturnwhateverthecommandprintedout(something) will run the command and return whatever the command printed out {varname} is for using variable names when you do not have spaces so you can do VAR1{VAR1}{VAR2}

So: VAR={VAR} = (echo $VAR)

"[ … ]” is syntactic sugar for the “test” command, meaning if you’re having problems with it, you can look through “man test”. Example usage:

if [ ${var}=YES ]

Accessing your bash_profile functions from a script

Section titled Accessing your bash_profile functions from a script

The best way I found was just to add “source ~/.bash_profile” to the top of whatever script you’re writing, e.g.:

~/.bash_profile:

#!/bin/bash

function foo() {

echo hi

}

test.sh:

#!/bin/bash

source ~/.bash_profile

foo

Using “sudo” inside of a script

Section titled Using “sudo” inside of a script

Don’t do something like this:

sudo -iu bldeploy

<some command that expects to be bldeploy>

Instead, do this:

sudo -u bldeploy impl.sh

<some command that expects to be bldeploy>

If you really wanted, you could follow the advice from here and do something like “sudo bash -c ‘cmd1; cmd2; cmd3’“. That link also has advice for using ‘here’ documents.

I had a string like this: <a bunch of garbage><a href=“http://127.0.0.1:8000/show/df8cd949661a”\>

I got the hash at the end by doing this:

if [[$curlOutput =~ show([a-zA-Z0-9]+)]]; then

bpasteHash=${BASH_REMATCH[1]}

else

echo “No match found”;

fi

The key was not using “w” like you have in other languages. I don’t think that works in Bash to represent a letter character.