Pragmatism in the real world

Extracting the base name of a file in Bash

I have a handy bash script that transcodes videos using Don Meton’s video_transcoding tools. This script was written in a hurry and one limitation it had was that it re-transcoded any source file even if the output file already existed.

The script looked like this:

#!/usr/bin/env bash

readonly source_dir="${1:-MKV}"
readonly output_dir="${2:-MP4}"

for file in "$source_dir"/*.mkv; do
    ./transcode.sh "$file" "$output_dir"
done

What it should do is only run transcode.sh if the output file doesn’t exist, so I updated it.

Parameter expansion to the rescue!

I needed to extract the base name and then construct the target filename in order to test for its existence. To extract the base name of the file, I used parameter expansion:

filename=${file##*/}
basename=${filename%.*}

The first line removes the directory portion of file, This works by using the ##[word] expansion which removes the largest prefix pattern which means that for */ it removes everything up to the last / in the string – i.e. any directory paths.

The second line removes the extension from the filename by using the %[word] expansion. This removes the smallest suffix pattern on the string: for .*, it removes the from the last . to the end of the string, and so removes the extension.

I can then create the target by concatenating the output_dir, basename and the .mp4 extension and then test for the target file’s existence.

The final script is now:

#!/usr/bin/env bash

readonly source_dir="${1:-MKV}"
readonly output_dir="${2:-MP4}"

for file in "$source_dir"/*.jpg; do
    filename=${file##*/}
    basename=${filename%.*}
    target="${output_dir}/${basename}.mp4"
    if [ ! -f "$target" ]; then
        ./transcode.sh "$file" "$output_dir"
    fi
done

It’s a small change, but makes my life easier as I can run my script multiple times without needing to ensure that I’ve deleted any the source files for files that I’ve already transcoded.