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.