How to:

Let’s consider a simple Bash script that needs some refactoring. It’s clunky, with repeated code and it’s hard to follow:

echo "Enter a filename:"
read filename
if [ -f "$filename" ]; then
    echo "File exists."
    count=$(grep -c "foo" "$filename")
    echo "The word foo appears $count times."
    echo "File does not exist."

Refactoring for clarity and reusability might involve introducing functions and handling errors more gracefully:


function file_exists() {
    [[ -f "$1" ]]

function count_occurrences() {
    grep -c "$1" "$2"

function main() {
    local filename word count
    echo "Enter a filename:"
    read -r filename
    echo "Enter the word to search for:"
    read -r word

    if file_exists "$filename"; then
        count=$(count_occurrences "$word" "$filename")
        echo "The word $word appears $count times."
        echo "File does not exist." >&2
        exit 1

main "$@"

The refactored version uses functions to improve readability and enables potential reuse.

Deep Dive:

Refactoring isn’t a concept that originated with Bash or even high-level programming languages; it’s as old as programming itself. The term was formalized in the book “Refactoring: Improving the Design of Existing Code” by Martin Fowler in 1999, focusing mainly on object-oriented languages.

In the context of Bash scripting, refactoring often means breaking down long scripts into functions, reducing repetition with loops or conditionals, and avoiding common pitfalls like failing to handle whitespace in filenames. Alternatives to Bash for scripts that have grown too complex include Python or Perl, which offer better data structures and error handling for complex tasks.

Bash-specific refactoring is more about adhering to best practices, such as quoting variables, using [[ ]] for tests over [ ], and preferring printf over echo for robust output. Implementation details often revolve around adhering to the style guides and using tools like shellcheck for static analysis to catch common mistakes.

See Also: