70 %
Chris Biscardi

bash script for creating new cargo packages

While building Rust Adventure (a temporary name for a Rust course I'm working on), I found running cargo new repeatedly, so I decided to automate it with bash.

tldr: the script

new-adventure.sh
shell
#!/bin/bash
set -euxo pipefail
LAST_FILE=`fd ^a- 'adventures/' -t d -d 1 | sort | tail -n 1`
regex="^adventures/a-([[:digit:]]+)"
if [[ $LAST_FILE =~ $regex ]]; then
number="${BASH_REMATCH[1]}"
number=$((number+1))
formatted_number=$(printf "%03d" $number)
cargo new adventures/a-$formatted_number-$1 --name $1
else
echo "$LAST_FILE doesn't match $regex"
fi

Explanation

Using fd, we can see a sample layout for the project. In ./adventures we have a set of cargo crates as a workspace.

shell
➜ fd . adventures/ -d1
adventures/a-001-hello-world
adventures/a-002-variables
adventures/a-003-mutable-variables
adventures/a-004-variable-types

First in our script we set the shebang, aka the #!, to use the bash interpreter. (yes it's really called that, no I don't know why).

shell
#!/bin/bash

Then we set -euxo pipefail, which other people have already explained well. TLDR; it makes our bash script a bit safer to run, and prints each command as it executes.

shell
set -euxo pipefail

Now we start getting into our script logic. We set LAST_FILE equal to the last result in a sorted list created by fd. This gives us the "highest number" in the filenames, since they're effectively sorted by number.

The fd is looking for directories (-t d) one level deep (-d 1) in adventures that match ^a-.

shell
LAST_FILE=`fd ^a- 'adventures/' -t d -d 1 | sort | tail -n 1`

We want to get the highest number from the list of directories and increment it. To do this we'll use regex. To regex in bash, we can set up our regex as a variable with a string. This looks for any numbers after adventures/a- and puts them in a capture group.

shell
regex="^adventures/a-([[:digit:]]+)"

Then test our filename against the regex to see if it matches.

shell
if [[ $LAST_FILE =~ $regex ]]; then
# it matched the regex, so we can use the capture group now
else
echo "$LAST_FILE doesn't match $regex"
fi

I put the capture group result into a new variable number for clarity, then add 1 to it. We can overwrite the variable here because we won't be using the original again.

shell
number="${BASH_REMATCH[1]}"
number=$((number+1))

If we leave the number as-is, then any number below 100 won't have three digits. To fix this we can use printf to pad the number with leading zeroes. This leaves us with numbers like 005 or 040.

shell
formatted_number=$(printf "%03d" $number)

and finally we can use our variables along with a positional argument to the script to instantiate our cargo new.

cargo new adventures/a-$formatted_number-$1 --name $1

Sample Output

The output could look like this.

output.txt
shell
➜ ./scripts/new-adventure.sh something
++ fd '^a-' adventures/ -t d -d 1
++ sort
++ tail -n 1
+ LAST_FILE=adventures/a-004-variable-types
+ regex='^adventures/a-([[:digit:]]+)'
+ [[ adventures/a-004-variable-types =~ ^adventures/a-([[:digit:]]+) ]]
+ number=004
+ number=5
++ printf %03d 5
+ formatted_number=005
+ cargo new adventures/a-005-something --name something
Created binary (application) `something` package