1

5 Bash Coding Techniques That Every Programmer Should Know

 1 year ago
source link: https://levelup.gitconnected.com/5-bash-coding-techniques-that-every-programmer-should-know-f63b11b59e8d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

5 Bash Coding Techniques That Every Programmer Should Know

Use these coding techniques to write fast and readable shell scripts

1*MAp4GBVLncS6putcVB51PQ.png
Photo by Emile Perron on Unsplash, edited with Canva

Bash undoubtedly became every modern Unix-like or Unix-based operating system’s native, inbuilt automation solution. Programmers use Bash to automate their repetitive command-line tasks by creating shell scripts. The primary goal of Bash is to offer a minimal syntax to execute other programs and work with their exit codes and output. But, modern Bash interpreters come with a fully-featured command language that offers most general-purpose programming language features. So, we can write highly-readable shell scripts by including traditional command-line invocations and algorithmic code. Modern Bash versions introduced associative arrays and performance-related features like pass-by-reference support to make Bash competitive with other shell-scripting-ready languages.

In this story, I will explain some Bash coding techniques you can include in your shell scripts to make them modern, fast, and readable. With these techniques, you can use Bash to write general-purpose programming or algorithm implementations, such as algorithm prototyping, implementing utility programs, or even competitive programming!

Using arrays in shell scripts

A traditional Bash variable typically has no type, but you can process it as an integer, decimal, or string according to a particular processing context. We usually use Bash variables to store command outputs, algorithm parameters, and other temporary values. Bash also supports two array types: one-dimensional (numerically indexed) and associative (key-value structure). Working with Bash arrays is easy as with other popular dynamically-typed general-purpose languages, such as Python, PHP, or JavaScript. See how to create arrays in Bash:

#!/bin/bash

numbers=(2 4 5 2)

declare -a words
words[0]='Orange'
words[1]='Pineapple'

echo ${numbers[@]} ${words[@]}

The above code outputs array contents as follows:

1*vbb3TQfYFgpB-cy9tqPrUg.png
Working with Bash arrays, a screenshot by the author

You can check the declaration of each array reference with the declare built-in as follows:

1*cg3XlgPfOLQCwpZp7rtNsg.png
Checking array declarations in Bash, a screenshot by the author

You can also do array manipulation and handling activities, such as appending new items, removing existing items, processing array elements, sorting, etc., with minimal syntaxes. For example, the following code removes invalid scores values and prints the highest first three scores:

#!/bin/bash

declare -a marks
marks+=(75 65 80 102 26) # class A marks
marks+=(103 68) # class B marks

# Remove invalid marks
for i in "${!marks[@]}"; do
if ((marks[i] > 100)); then
unset "marks[$i]"
fi
done

# Sort all marks
marks_s=($(printf '%s\n' "${marks[@]}" | sort -nr))

# Prints the top-3
echo ${marks_s[0]} ${marks_s[1]} ${marks_s[2]}

The above code spawns a separate process for sorting since we used the sort external command, but you can avoid it by implementing a simple sorting algorithm like selection sort with some Bash code.

Creating maps or dictionaries

In some coding scenarios, we have to store key-value pair data in our shell scripts. Programmers typically use key-value data structures to create dictionary structures, maps, and caching containers (via memoization). If you use Python to write your shell scripts, you can use the inbuilt dictionary data structure for storing key-value data. How to use a dictionary structure in Bash?

Bash introduced the associative array feature for storing key-value data in version 4.0. Let’s see a simple example for Bash associative arrays:

#!/bin/bash

declare -A marks=([john]=75 [doe]=82 [ann]=83 [ava]=72)

for key in "${!marks[@]}"; do
printf "$key \t ${marks[$key]} \n"
done

Here we used the !mapvar[@] syntax to extract all dictionary keys as an array for iteration. The above code prints all keys and values as follows:

1*vHsv2sWpFsSFyJx6sMt7KQ.png
Working with Bash associative arrays, a screenshot by the author

Bash lets you use minimal syntax to manipulate and access associative array data. Working with Bash associative arrays is like working with Python dictionaries. Look at the following example:

#!/bin/bash

read -p "Enter coords (i.e., [x]=10 [y]=12): " coords
declare -A "coords=($coords)"

if [ ! -v "coords[x]" ]; then
coords[x]=5
fi

if [ ! -v "coords[y]" ]; then
coords[y]=10
fi

for key in "${!coords[@]}"; do
printf "$key = ${coords[$key]} \n"
done

The above source code asks for x and y coordinates from the user, sets default values for missing axis values, and prints them on the terminal. Here we used the ! -v syntax as we typically use not in with Python dictionaries.

Implementing named arguments support

When you execute a shell script via the Bash interpreter, the operating system creates a new Bash process with your script file as the first command-line argument. Operating systems usually let you pass a sequence of arguments into each operating system process. As you provide command-line arguments for other commands/processes, you can pass them into your Bash script too. Assume that you need to pass two integer values to a script. Then, you can easily use $1 and $2 to access the first and second argument values, respectively. But, things will get complex when you use more indexed arguments, and you need to implement optional arguments (aka command-line flags or options).

As a solution for this scenario, you can use named arguments with the getopts built-in. With the following shell script, we can override some default in-script values:

#!/bin/bash

title="Information"
message="Hello world"

while getopts ":t:m:" option; do
echo $option
case "${option}" in
t)
title=${OPTARG}
;;
m)
message=${OPTARG}
;;
esac
done

zenity --info --title="$title" --text="$message"

By default, the above script shows a GTK message box with a default title and message, but you can override them with named command-line arguments, as shown in the following preview:

1*p-HtyYsPSUEWSrMAW-TCTA.gif
Using named arguments in a Bash script, a screenshot by the author

The getopts builtin supports using one-letter options only. You can use getopt to use long-form options (i.e.,--title), as demonstrated in this Gist. Learn more about adding GUI elements in Bash scripts with the following story:

Using pass-by-reference in functions

Pass-by-reference is a programming language feature that lets you pass data into functions via memory references, rather than copying whole data segments into new variables. C++ programmers always strive to write performance-first code using pass-by-reference over pass-by-value for class objects, structs, and strings.

If you use Bash 4.3 or a newer version, you can use name references to implement pass-by-reference in shell scripts. Here is a simple example code snippet that changes a string variable via a function:

#!/bin/bash

function change_str_var() {
local str_new="Bash"
local -n str_ref=$1
echo "$str_ref -> $str_new" # Python -> Bash
str_ref=$str_new
}

str="Python"
change_str_var str
echo $str # Bash

The above change_str_var function creates a local str_ref reference to the global str with the local command. Then, it assigns a new string value by overriding the old string value.

Some programmers use the echo command within a function and call the particular function via the command-substitution feature to return a value from a Bash function (since the native Bash return keyword only supports returning valid exit codes). It spawns another child shell and consumes more resources. So, now programmers can use pass-by-reference and write performance-first Bash function returns if they use a new Bash version.

Using type and modifier attributes for variables

Bash is known as an untyped command language. In other words, it typically handles variable data as strings but handles them accordingly based on the context (i.e., in arithmetic expansions). On the other hand, Bash also lets programmers use type attributes and provides two inbuilt array types. Even with these features, we can’t identify Bash as a purely dynamically-typed language, but these variable attributes place Bash somewhere between untyped and dynamically-typed language ends.

Bash supports marking a particular variable as an integer using the integer variable attribute. Once you create an integer variable, Bash warns you when you assign non-integer values as follows:

1*vO_GG8ufqpmhZrFjvZ1LOg.png
Adding the integer attribute to a Bash variable, a screenshot by the author

Bash also lets you create constants using the declare -r command. Whenever your script attempts to change a constant, Bash prints an error message on the screen. Moreover, as we used before, you can create arrays with the declare builtin.

Bash also lets you add some modifier attributes to variables. For example, you can make lowercase-only or uppercase-only strings as follows:

#!/bin/bash

declare -l lc_str="Hello World"
declare -u uc_str
uc_str="Hello"
uc_str="World"

echo $lc_str # hello world
echo $uc_str # WORLD

You can write less-error-prone, readable, and modern shell scripts with Bash variable attributes.

Thanks for reading.

Level Up Coding

Thanks for being a part of our community! Before you go:

🚀👉 Join the Level Up talent collective and find an amazing job


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK