シェルスクリプトにて無駄に「いい感じの見た目」で変数展開されるように頑張った話

Super-microDXにインストールした河豚板をfiupdateするためにISOイメージが必要となるが、毎回URLを調べてダウンロードするのが面倒なので、ダウンロード用のシェルスクリプトdl-fuguita.shを作成して使い回すようにした。

#!/bin/sh
# -*- coding: utf-8-unix -*-
# vim:fileencoding=utf-8:ff=unix
# @(#) Download a FuguIta ISO image file.

set -u
umask 0022
IFS=$(printf ' \t\n_'); IFS=${IFS%_}
PATH=/bin:/usr/bin
export IFS LC_ALL=C LANG=C PATH

readonly progname="${0##*/}"
readonly version=1.0.0

# usage <exit-code>
usage() {
    echo "usage: $progname [-hv] <revision>" 1>&2
    exit "$1"
}

# version (no parameter)
version() {
    echo "$progname $version" 1>&2
    exit 0
}

# main routine

readonly url=https://jp2.dl.fuguita.org/LiveDVD/
readonly arch=i386
readonly ver=7.5

opt=
while getopts hv opt; do
    case $opt in
    h)      usage 0 ;;
    v)      version ;;
    \?)     usage 1 ;;
    esac
done
shift $((OPTIND - 1))

[ $# -eq 1 ] || usage 1
rev=$1

ftp $url{SHA256,FuguIta-$ver-$arch-$rev.iso.gz}

普段curl(1)やwget(1)を使っているLinuxユーザとしては、ftp(1)でHTTPサーバからファイル取得するのは、ちょっと不思議な感じだ。軽く調べた感じでは、BSD系のftp(1)はHTTPによるファイルダウンロードにも使えるらしい。

dl-fuguita.shを作ったことで、fiupdateする際に、次のような感じのワンライナーを実行すればよくなった。

# バージョン202408021にアップデートする場合:
( rev=202408021; ./dl-fuguita.sh $rev && fiupdate $rev )

このdl-fuguita.shに「ダウンロードが中断した場合に、後で続きからダウンロードできる」ための仕組みを追加したくなった。OpenBSDftp(1)では、オプション-Cを付ければよいらしい。そこで、シェルスクリプトのオプションに-Cがあったらそのままftp(1)に引き渡すようにしてみた。

実装はこんな感じ。今後、似たような「引数なしオプション」を増やしたいときに流用できるように、ほんの少しだけ凝った書き方をしている。

# 前略

ftpopt=

opt=
while getopts Chv opt; do
    case $opt in
    C)      ftpopt="$ftpopt -$opt" ;;
    h)      usage 0 ;;
    v)      version ;;
    \?)     usage 1 ;;
    esac
done
shift $((OPTIND - 1))

[ $# -eq 1 ] || usage 1
rev=$1

ftp $ftpopt $url{SHA256,FuguIta-$ver-$arch-$rev.iso.gz}

オプション-Cを2回以上指定されたケースを考慮していないが、自分用のスクリプトなので、手抜きでもOKとしている。

さて、こんな感じの実装で十分に機能するのだが、しかし個人的に最終行(ftp(1)を叩いている行)で$ftpoptを展開した結果が気になったのだ。

例えば、$ftpoptが空の場合、ftp$urlの間の空白が2文字になる。またオプション-Cを1回だけ指定した場合も、ftpの直後の空白が2文字になる。

この部分を、もっと、こう、無駄に「いい感じの見栄え」で展開されるようにできないか考えた結果、こんな感じで実現できることが分かった。

# 前略

ftpopt=

opt=
while getopts Chv opt; do
    case $opt in
    C)      ftpopt="${ftpopt:-}${ftpopt:+ }-$opt" ;;
    h)      usage 0 ;;
    v)      version ;;
    \?)     usage 1 ;;
    esac
done
shift $((OPTIND - 1))

[ $# -eq 1 ] || usage 1
rev=$1

ftp ${ftpopt:-}${ftpopt:+ }$url{SHA256,FuguIta-$ver-$arch-$rev.iso.gz}

${ftpopt:-}${ftpopt:+ }は、$ftpoptが未定義か空文字の場合には空文字に展開されて、$ftpoptに1文字以上の文字が設定されている場合には「$ftpoptの中身 + 1文字の空白文字」に展開される。

例えば最終行は、$ftpoptが空なら「ftp $url」のような感じになり、$ftpoptの中身が-Cなら「ftp -C $url」のような感じになる。空白が2文字にならずに済むのだ。

こんな感じに、いい感じの見た目に変数展開されるようになったが、特に意味はない。全くもって無駄な行為である。