Fun With Bash Scripting

What is it?

Bash is the Bourne Again SHell. It was made in 1989 as a replacement for System 7 UNIX’s Bourne shell and is part of the GNU project. Since then, it has become ubiquitous in the Linux world as the default shell in nearly every Linux distribution. For most Linux users, it’s worth while becoming acquainted with it. It’s power is in automating system management tasks and various other things.

Getting Setup

You’ll need a terminal emulator (anything will suffice) and a text editor (I recommend vim, but nano will do). Lets make our first basic script:

#!/usr/bin/env bash

echo 'Hello there'

This script prints the text Hello there and quits. It is a good habit to start all of your scripts with #!/usr/bin/env bash rather than #!/usr/bin/bash as it makes your scripts more portable to other systems (the BSDs in particular). We can fork this script and do some other things with it. For example, if we wanted it to take a name and then have the script say Hello to that person, we’d write:

#!/usr/bin/env bash

read -p "What's your name?: " name

echo "Hello there, $name"

Here, read is a built in bash function that will read a user input. The -p flag is to prompt. If the input is sensitive (i.e a password), use the -s flag to silence the input.
This script asks the user for their name and stores it at the name variable, then prints hello and exits. Note that double quotations were used here. That is because double quotes interpret variables, while single quotes always echo a string verbatim.

Easy enough, but this is rather banal. Lets get to making scripts that are of use

Useful Bash Scripts

As mentioned previously, bash scripts are very useful for automating system tasks. This is why it’s best used in conjunction with cron, the UNIX system scheduler. A number of Linux distributions don’t ship with a cron daemon (all of the BSDs do), so it may be necessary to pick a cron daemon yourself. I recommend dillon’s cron daemon as it’s very simple and can still handle things for desktop users like anachronistic execution. If your distribution lacks it, then I recommend cronie. This will enable you to run a script periodically at a set interval. In any case, lets proceed into some common scripts

System Backup

Anyone who has been screwed over by a hard drive failure knows the pain of losing data. Don’t be a schmuck, backup! Thankfully, it’s fairly trivial to make a bash script that does this for us. This is a script that allows us to make timestamped snapshots of our system to another location (preferably an external hard drive):

#!/usr/bin/env bash

snap_date=$(date +%m-%d-%Y)

read -p "Enter the directory you want to back up: " target
read -p "Enter the directory you want to store the backup in: " destination

tar -czpf $target | openssl enc -e -aes256 -salt -out $destination/$snap_date.tar.gz

Here, we set a variable called snap_date to set the date formatting (run man date for other ways to format). We create two variable for the directory we want to backup (target) and where we’re backing it up to (destination). We then use tar to compress the directory whilst preserving its permissions into a gzip archive with a file name matching the current date. This is then piped into openssl, which we use to encrypt this newly made tar ball with AES-256, salting the password.

We probably want a script that takes a given snapshot, decrypts it, and reinflates the archive:

#!/usr/bin/env bash

read -p "Enter the file name of the snapshot you want to migrate to: " snap
read -p "Enter the full path of where you want it: " destination

cp $snap $destination && cd $destination
openssl enc -d -aes256 -in $snap -out $snap

tar -xvpf $snap

Audio Conversion

The program ffmpeg is a highly versatile audio and video conversion tool. If you’re doing it frequently enough, it becomes tedious to enter in the flags repeatedly and remember them. A bash script can save the day!

#!/usr/bin/env bash

echo 'Enter the input file without the file extension: '; read input
echo 'What conversion do you want to do?: '
select opt in "MP3-OGG" "OGG-MP3" "WAV-FLAC" "FLAC-MP3" "FLAC-OGG" "Quit"; do
    case $opt in
        MP3-OGG)
            ffmpeg -i $input.mp3 -vn -ar 44100 -c:a libvorbis $input.ogg
            ;;
        OGG-MP3) ffmpeg -i $input.ogg -vn -ar 44100 -ac 2 -b:a 320k $input.mp3 break;;
        WAV-FLAC)
            ffmpeg -i $input.wav -c:a flac $input.flac
            ;;
        FLAC-MP3)
            ffmpeg -i $input.flac -vn -ar 44100 -b:a 320k $input.mp3
            ;;
        FLAC-OGG)
            ffmpeg -i $input.flac -vn -ar 44100 -c:a libvorbis $input.ogg
            ;;
        Quit)
            echo 'Exiting...'
            break;;
        *)
            echo 'Bad input, try again'
            ;;
    esac
done

Webcam Recording

For screencasts, it’s often nice to have a window of your webcam output in the corner of the screen so viewers can see you talk. To do this, we can set up a script called webcam that spawns an mpv window of the webcam output:

#!/usr/bin/env bash

read -p "Enter resolution width: " width
read -p "Enter resolution height: " height

v4l2-ctl -d /dev/video0 --set-fmt-video=width="$width",height="$height" # video4linux2 needs to have resolution preset

mpv --demuxer-lavf-format=video4linux2 --demuxer-lavf-o-set=input_format=mjpeg av://v4l2:/dev/video0

If you intend to always record at a specific resolution, just make this a simple two line shell script comprised of the last two lines.

System Wide Adblocking

Ads are pesky and often track users as well. Getting rid of them in one browser is often trivial enough with uBlock Origin, however, it won’t block all ads on your device. For this, we can leverage the hosts file:

#!/bin/sh

# Check if script is being run by root or with sudo, if not, then fail
if [ "$(id -u)" -ne 0 ]; then
	# Print error message
	echo "This must be run as root. Do 'sudo !!' to try again." >&2
	exit 1 # Exit with error status
fi

# Make a backup if one doesn't already exist
[ ! -e /etc/hosts.bk ] && cp /etc/hosts /etc/hosts.bk

# Download the new hosts file, overwriting the original
wget -nv -O /etc/hosts -- \
https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts

# Append reddit's tracking addresses
echo "0.0.0.0 events.redditmedia.com" >> /etc/hosts
echo "0.0.0.0 out.reddit.com" >> /etc/hosts

The top is begun with #!/bin/sh as this is a fully POSIX compliant script.

ISO image flasher

The UNIX command dd or disk dump (affectionately called disk destroyer for those unfortunate oafs who don’t properly set source and target) is very powerful and has great use in copying a large set of files to a certain location. One of those most felicitous applications along this line is flashing an ISO image to a usb stick so it can be used as a bootable medium for an OS installer or live OS. This is a script for flashing ISO images to different mediums:

#!/usr/bin/env bash

if [ "$(id -u)" -ne 0 ]; then
	# Print error message
	echo "This must be run as root. Do 'sudo !!' to try again." >&2
	exit 1 # Exit with error status
fi

read -p "Enter the full path to the iso image you want: " iso
read -p "Enter the device name you want to flash the image to: " device

mkfs.vfat -F32 "$device" -I
dd bs=4M if="$iso" | pv -tpre | dd of="$device" && sync

Here, the image and target device are taken as arguments and then the device is formatted to the FAT32 file system and then subsequently flashed with the given iso image with a progress bar. You will need to install pv to your Linux distribution though in order to make use of it.

Storage Benchmark

Sometimes, it’s nice knowing how well your hard drive or SSD are performing. Our good old friend dd can once again come to the rescue here:

#!/usr/bin/env bash

if [ "$(id -u)" -ne 0 ]; then
	# Print error message
	echo "This must be run as root. Do 'sudo !!' to try again." >&2
	exit 1 # Exit with error status
fi

echo 'Testing write speeds...'
dd if=/dev/zero of=/tmp/demo.img bs=64M count=16 conv=fdatasync 

echo 'Testing read speeds...'
sysctl -w vm.drop_caches=3
dd if=/tmp/demo.img of=/dev/zero bs=64M count=16 oflag=direct

Managing QEMU

QEMU with KVM is a fantastic virtualization suite. Many people will use a clunky front end like libvirt or the VM Manager GUI. These are superfluous and QEMU can be managed very simply with bash scripts. This is a creation script that I’ve made for frills free setup of new VMs:

#!/usr/bin/env bash

# This function sets up a bridge interface
bridge() {
    echo 'allow br0' | sudo tee -a /etc/qemu/bridge.conf
    sudo ip link add name br0 type bridge
    sudo ip link set br0 up
    sudo ip link set eth0 master br0
}

echo -e "Welcome to QEMU VM setup!\n"

# This script takes a file name as an argument, so we're just writing the beginning of the script we're generating here
echo -e '#!/usr/bin/env bash\n\nOPTS=""' >> "$1".sh

# Asking for core count
read -p "How many cores do you want to VM to have?: " cores
echo -e "\n# CPU\nOPTS=\"\$OPTS -cpu host -enable-kvm -smp $cores\"" >> "$1".sh # We're appending parameters to this shell script we're making

# Asking for amount of memory
read -p "How much RAM in Gigabytes?: " ram
echo -e "# RAM\nOPTS=\"\$OPTS -m ${ram}G\"" >> "$1".sh

# Switch statement for networking
echo 'What networking setup do you want?: '
select pick in "User" "SSH" "Bridged"; do
    case $pick in
        User)
            echo 'Moving on...' # User networking is the default, so we do nothing here
            break;;
        SSH)
            echo 'Setting up SSH at port 2222...'
            echo -e "# Networking\nOPTS=\"\$OPTS -device e1000,netdev=net0 -netdev user,id=net0,hostfwd=tcp::2222-:22\"" >> "$1".sh # Creating virtual network and binding host tcp 2222 to 22
            break;;
        Bridged)
            echo 'Creating new bridge br0...' 
            bridge # We're calling the bridge function we instantiated earlier
            break;;
        *)
            echo 'Invalid choice, try again...'
            ;;
    esac
done

read -p "Enter the path to the installation ISO: " iso
read -p "What disk format do you want (enter qcow or raw)?: " fmt
read -p "Enter disk size in Gigabytes: " size
qemu-img create -f $fmt "$1".$fmt ${size}G
echo -e "# Disks\nOPTS=\"\$OPTS -cdrom $iso -drive file="$1".$fmt,if=virtio\"" >> "$1".sh

echo 'What graphics options will you choose?: '
select grp in "std" "spice" "virtio" "none"; do
    case $grp in
        std)
            echo 'Using std...' # std is the default, so we don't have to do anything
            break;;
        spice)
            echo 'Using spice...'
            echo -e "# Graphics\nOPTS=\"\$OPTS -vga qxl -device virtio-serial-pci -spice unix,addr=/tmp/vm_spice.socket,disable-ticketing -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 -chardev spicevmc,id=spicechannel0,name=vdagent\"" >> "$1".sh # This will launch spice and enable copy paste support for the session
            break;;
        virtio)
            echo 'Using virtio'
            echo -e "# Graphics\nOPTS=\"\$OPTS -vga virtio -display sdl,gl=on\"" >> "$1".sh
            break;;
        none)
            echo 'Disabling graphics'
            echo -e "# Graphics\nOPTS=\"\$OPTS -vga none\"" >> "$1".sh
            break;;
        *)
            echo 'Invalid choice, try again...'
            ;;
    esac
done

read -p 'Audio? (y,n): ' audio
if [[ "$audio" == y ]]; then
    echo 'Adding audio support...'
    echo -e "# Audio\nOPTS=\"\$OPTS -soundhw hda\"" >> "$1".sh # Emulating an intel soundcard
elif [[ "$audio" == n ]]; then
    echo 'Proceeding without audio support...'
else
    echo 'Invalid choice, moving on...'
fi

read -p 'Using Windows? (y,n): ' win
if [[ "$win" == y ]]; then
    echo -e "# Fix clock\nOPTS=\"\$OPTS -rtc clock=host,base=localtime\"\n# Mouse\nOPTS=\"\$OPTS -usb -device usb-tablet\"" >> "$1".sh # We're resolving time conflicts & fixing mouse issues
elif [[ "$win" == n ]]; then
    echo 'Proceeding...'
else
    echo 'Invalid choice, moving on...'
fi

echo -e "\n# Run\nqemu-system-x86_64 \$OPTS" >> "$1".sh # Writing the final line for the script we're making that runs all of the parameters we've set
echo 'Finished setup. Have a nice day!'

Further Scripting

There are far more things that can be done with bash, but these are just a few useful things. For a further deep dive into bash specifics, you can’t go wrong with the bash cheatsheet. Beyond this, using the manual page by running man bash will give a well detailed guide on bash.