Intro to shell scripting

Introduction

Shell scripts are a useful tool for automating repetitive or complex tasks. This module will teach the development of Bash scripts.

Tutorial

Here is a tutorial on Bash scripting that I reference often for learning various I need for tasks I'm working on. This provides an interesting background for regular expressions and how they work.

Useful commands

find and sed

find . -type f -name \*\.cpp -exec sed -i 's/Solenoid/frc::Solenoid/g' {} \;

find . -type f -name \*\.cpp recursively searches for files whose name ends with ".cpp" starting from the current directory.

sed -i 's/Solenoid/frc::Solenoid/g' {} replaces instances of "Solenoid" with "frc::Solenoid" where {} is where find puts the filename. /g makes sed replace all occurences on a given line.

Recursive grep

grep -Rn Solenoid recursively searches for files starting from the current directory for the pattern "Solenoid". It lists the filename, line number, and line content of any matches.

Examples

The following scripts use git commands to manipulate a repository's history. The second uses the plumbing command for-each-ref to enumerate all local branches.

loc-generate.sh (Checks out each commit in a Git repository's history and generates a CSV file of number of lines of code in the repository)

#!/bin/bash

git switch -q main
read -r -a array > data.csv
  else
    echo $x$','$y','$x$','$y >> data.csv
  fi
done

# Print newline so prompt appears on next line
echo

mirror.sh (Used for management of 20+ branches in allwpilib Git repository)

#!/bin/bash

# ./mirror.sh COMMAND STARTBRANCH
# 1st argument is command to run
# 2nd argument is starting branch

if [ $# -eq 0 ]; then
  exit 1
fi

# If a starting branch was provided, don't run until that branch is checked out
if [ $# -eq 2 ]; then
  run=false
else
  run=true
fi

if [ "$1" == "rebase" ]; then
  git switch -q main
  git pull -q upstream main
fi

branches=()
eval "$(git for-each-ref --shell --format='branches+=(%(refname))' refs/heads/)"
for branch in "${branches[@]}"; do
  base=$(basename $branch)
  if [ "$run" = false ]; then
    if [ "$base" == "$2" ]; then
      run=true
    fi
  fi

  if [ "$run" = true ]; then
    echo "Switching to branch '$base'"
    git reset -q --hard HEAD
    git switch -q $base

    # Run subcommand
    case $1 in
    "build")
      if [ "$base" == "itable-base" ] ||
         [ "$base" == "kalman-filter" ] ||
         [ "$base" == "main" ] ||
         [ "$base" == "motion-profile" ] ||
         [ "$base" == "pid-dt" ]; then
        continue
      fi

      git apply allwpilib-gradle.patch

      ./gradlew build
      ;;
    "buildsim")
      if [ "$base" == "itable-base" ] ||
         [ "$base" == "kalman-filter" ] ||
         [ "$base" == "main" ] ||
         [ "$base" == "motion-profile" ] ||
         [ "$base" == "pid-dt" ]; then
        continue
      fi

      git apply allwpilib-gradle.patch

      ./gradlew build -PmakeSim
      ;;
    "format")
      ./styleguide/format.py
      ./license-update.py
      if [ `git diff | wc -l` != "0" ]; then
        echo "branch '$base' modified by format.py"
        exit 1
      fi
      ;;
    "push")
      git push -f
      ;;
    "rebase")
      if [ "$base" != "cap-sys-nice" ] &&
         [ "$base" != "cleanup-command" ] &&
         [ "$base" != "cleanup-const" ] &&
         [ "$base" != "getinstance" ] &&
         [ "$base" != "itable-base" ] &&
         [ "$base" != "motor-safety-base" ] &&
         [ "$base" != "pid-filter" ]; then
        git rebase main
      fi
      ;;
    "setorigin")
      git branch --set-upstream-to origin/$base
      ;;
    *)
      echo '$1' not a valid subcommand
      exit 1
      ;;
    esac

    if [ $? -ne 0 ]; then
      exit 1
    fi
  fi
done