Today I wrote a simple shell script to scratch an itch: trimming away advertisements from podcasts files I download. After spending half an hour fixing up a version I liked, I decided to spend another half an hour writing about it, because even such a simple task has a lot more context than it seems.
Here is the script:
#!/usr/bin/env sh
# Trims a number of seconds from the start of a media file
# Requires: trash-cli, ffmpeg
if [ -z "$1" ]; then
printf "Usage: trim_ad.sh <file> <seconds>"
exit 1
fi
# source file
src=${1}
# how many seconds to trim the beginning of the file
ss=${2}
tmp=$(mktemp)
ext="${src##*.}"
tmpfile="${tmp}.${ext}"
printf "\nFile: %s" "$src"
printf "\nTmp: %s" "$tmpfile"
printf "\nTrim: %s seconds\n" "$ss"
ffmpeg -loglevel quiet -i "$src" -y -ss "$ss" -c:v copy -c:a copy "$tmpfile"
trash-put "$src"
mv "$tmpfile" "$src"
printf "\nDONE. File has been trimmed. Original file has been put into trash\n"
Now let's quickly go through every single small wrinkle I encountered.
0. I want the script to be compatible with the base common denominator and not only with some dialect, like Bash.
#!/usr/bin/env sh
Bash has become very widespread but unless really necessary, I don't want to make a script incompatible with the rest of the world. Debian symlinks /bin/sh to /bin/dash which, according to the man page, "[dash] in the process of being changed to conform with the POSIX [...] specifications for the shell. This version [...] is not a Korn shell clone [...] Only features designated by POSIX, plus a few Berkeley extensions, are being incorporated into this shell.".
Just by reading this excerpt I get a feeling that shell interpreters in Linux are a cesspool of pain I don't want to get into. The less I know, the better I sleep at night.
Also, it is preferred to invoke /usr/bin/env to have your system pick the shell interpreter, instead of make assumptions like /usr/bin/sh or /usr/bin/bash.
1. A bit of documentation for my future self and possibly for others.
# Trims a number of seconds from the start of a media file
# Requires: trash-cli, ffmpeg
I want to immediately convey what this script does and what it needs. Not so easy to summarize in a single sentence the purpose, for a non English mother-tongue.
2. Add blank lines to let people breath when reading this script.
<many blank lines>
I hate when people condense and minify code to the extreme. Like, do you pay disk space by the byte?? Be kind to me, dear stranger!
3. Add some basic input validation.
if [ -z "$1" ]; then
printf "Usage: trim_ad.sh <file> <seconds>"
exit 1
fi
It could be more sophisticated but I added it at the end and I was already bored.
4. Explain the input parameters. Be kind to your future self.
# source file
src=${1}
# how many seconds to trim from the beginning of the file
ss=${2}
5. We have cool core utilities to create temporary files and directories. Let's use them.
tmp=$(mktemp)
6. Search for the 30th time on Stack Overflow how to extract the extension from a filename.
ext="${src##*.}"
tmpfile="${tmp}.${ext}"
Tried to follow the links and read the documentation but it's terse. Only after been spoonfed the answer I need, I can understand the documentation.
7. Explain what's happening.
printf "\nFile: %s" "$src"
printf "\nTmp: %s" "$tmpfile"
printf "\nTrim: %s seconds\n" "$ss"
I learned a few months ago that it is recommended using printf instead of echo for printing. Since I forgot why, I had to look it up (again!). The best first answer on Stack Overflow says that 1) echo always returns status 0 even on errors and that 2) printf allows string formatting.
8. ffmpeg is the pillar and the crux of Linux tooling.
ffmpeg -loglevel quiet -i "$src" -y -ss "$ss" -c:v copy -c:a copy "$tmpfile"
Extremely powerful but impossible to use without looking every single time at the documentation or
at some random article on the internet. I have a list of shell aliases that are just mnemonic reminders
of the features I use most (e.g. "ffmpeg-trim-beginning", "ffmpeg-remove-audio-stream"). Yes, I also have a list of websites with cheatsheets. Even with these
helpers, I always have a moment of pause before invoking ffmpeg.
Also, always quote parameters in shell scripts, you never know what the user (i.e. the future you!) will input into this script.
Worth mentioning that using shellcheck as linter helps a lot.
9. trash-cli is a nifty command line interface to the freedesktop.org trashcan.
trash-put "$src"
Since the script is executing a destrutive operation, I prefer not deleting the original file. And since trash-cli is an additional dependency, people should be aware of that at the beginning of the script.
10. Some context when finishing the operation.
printf "\nDONE. File has been trimmed. Original file has been put into trash\n"
Again, be nice and explicit.
§ Conclusions
The takeaway of this little story is that even a simple script file can have a lot of gotchas and could ask for more thinking one might expect. It's a useful exercise to keep in mind when writing any kind of code.
Also, writing this blog post took 1h30', exactly three times more than I estimated at the beginning. Then people wonder why it is hard to estimate software development.