LinuxでLiveUSBを作る手間を減らすために作成したシェルスクリプトを公開します

ソースコードは記事下段に記載していますのでファイルに保存してお使い下さい。

# 実行方法
下記コマンドを実行します。

```
   sudo sh "createLiveMedia.shのパス" "isoファイルのパス"
```

isoファイルとしていますが、ファイルであればそれ以外でも可能です。

# 操作方法
(1)下記のように接続しているデバイスの一覧が表示されるので、書き込むデバイスのインデックスを入力します（表示されるデバイスはお使いの環境で変わります）。

```
INDEX TRAN     SIZE MODEL            NAME
    1 sata   465.8G HGST HTS545050A7 /dev/sda
    2 sata   119.2G SSM128GPTC0-S91  /dev/sdb
    3 usb      2.7T HDCZ-UT          /dev/sdc
    4 usb     60.4G Multi-Card       /dev/sdd
    5 usb      3.8G Cruzer Colors+   /dev/sde
    6 sata    1024M DVD-RAM UJ8E2    /dev/sr0
書き込みを行うデバイスのインデックスを指定して下さい：
```

(2)下記のようにマウントしているパーティションの一覧と、アンマウントして良いかの確認メッセージが表示されます（マウントしているパーティションが存在しない場合はスキップされます）。

```
/dev/sde1
マウントされているパーティションが存在します。アンマウントしてよろしいですか？[y/n]：
```

(3)書き込んで良いか確認メッセージが表示されます。

```
指定されたデバイスのデータは全て削除されます。よろしいですか？[y/n]：
```

(4)書込みが完了すると「書き込みが完了しました。」と表示されます。

# 適用ライセンス
本シェルスクリプトは教科書ライセンス（独自ライセンス）で公開します。

```
本製品の利用をもって教科書ライセンス（詳細は下記条文を参照）を了承したものと見做します。

（利用条件）本製品を利用するにあたり従わなければならない条件はありません。
（免責1）本製品を利用した事で生じた如何なる損害に対して、本製品の権利者にその責は及ばないものとします。
（免責2）本製品がどのような場合でも使用できる事を保証しません。

（想定問答）
Q：どのような行為が「本製品を利用する」に該当するか。
A：本製品が関係する全ての行為が該当します。
```

# ソースコード
```shellscript:createLiveMedia.sh
#!/bin/sh
#このシェルスクリプトは管理者権限が必要な操作（umountコマンド及びデバイスファイルへの書き込み）を含むため、実行時に管理者権限を要求する。
#umountコマンドは必須ではないが、アンマウントしない場合は処理完了後のLinuxの該当デバイスの認識がおかしくなる（存在しないパーティションをマウントしている扱いになる）。
#syncコマンドは必須ではないが、遅延書き込みが行われていた場合に書き込み途中にも関わらず完了したと表示する可能性がある。
#光学メディアへの書き込みも可能だが限定的（正常に書き込めるのはDVD+RW, DVD-RAM, BD-REの3種類のみ。但しいずれも未フォーマットの状態では書き込み不可）である。

println(){
    printf "%s" "$1"
    echo ""
}

throwException(){
    printf "%s" "$1"
    echo ""
    exit 1
}

[ "$(uname)" != "Linux" ] && throwException "このシェルスクリプトはLinuxでしか使えないコマンドを含むため、お使いのOSでは実行できません。"
[ -z "$(type lsblk 2> /dev/null)" ] && throwException "お使いの環境にはlsblkコマンドがインストールされていません。"
[ -z "$(type umount 2> /dev/null)" ] && throwException "お使いの環境にはumountコマンドがインストールされていません。"
[ -z "$(type sync 2> /dev/null)" ] && throwException "お使いの環境にはsyncコマンドがインストールされていません。"
[ $# -ne 1 ] && throwException "引数の数が不正です。引数の数は常に1でなければなりません。"

isoFile=$1
[ ! -e "${isoFile}" ] && throwException "指定されたファイルは見つかりませんでした。"
[ "$(id -u)" -ne 0 ] && throwException "このシェルスクリプトには管理者権限が必要な操作が含まれる為、一般ユーザーでは実行できません。"

view="$(lsblk -d -l -p -o TRAN,SIZE,MODEL,NAME)"
view="$(println "${view}" | nl -v 0 -w 5 -s " " | sed -e "1 s/^ *0/INDEX/")"
deviceList="$(println "${view}" | sed -e "1d" | sed -e "s/^.* \{1,\}//")"
[ -z "${deviceList}" ] && throwException "選択可能なデバイスがありませんでした。"

println "${view}"
printf "%s" "書き込みを行うデバイスのインデックスを指定して下さい："
read -r index
index="$(println "${index}" | grep "^[1-9][0-9]*$")"
[ -z "${index}" ] && throwException "INDEX列に表示されている数字を入力して下さい。"
[ "${index}" -gt "$(println "${deviceList}" | wc -l)" ] && throwException "INDEX列に表示されている数字を入力して下さい。"

deviceFile="$(println "${deviceList}" | sed -n "${index}p")"
partitionList="$(lsblk "${deviceFile}" -l -n -p -o NAME)"
partitionCount="$(println "${partitionList}" | wc -l)"
while [ "${partitionCount}" -ge 1 ]; do
    partitionFile="$(println "${partitionList}" | sed -n "${partitionCount}p")"
    mountDir="$(lsblk "${partitionFile}" -d -l -n -o MOUNTPOINT 2> /dev/null)"
    if [ -z "${mountDir}" ]; then
        partitionList="$(println "${partitionList}" | sed -e "${partitionCount}d")"
    fi
    partitionCount="$((partitionCount - 1))"
done
if [ -n "${partitionList}" ]; then
    println "${partitionList}"
    printf "%s" "マウントされているパーティションが存在します。アンマウントしてよろしいですか？[y/n]："
    read -r canUnmount
    canUnmount="$(println "${canUnmount}" | grep "^[Yy]$")"
    [ -z "${canUnmount}" ] && throwException "y以外が入力された為、処理を中止しました。"

    partitionCount="$(println "${partitionList}" | wc -l)"
    while [ "${partitionCount}" -ge 1 ]; do
        partitionFile="$(println "${partitionList}" | sed -n "${partitionCount}p")"
        [ -n "$(umount "${partitionFile}" 2>&1)" ] && throwException "アンマウント中にエラーが発生した為、処理を中止しました。"

        partitionCount="$((partitionCount - 1))"
    done
fi

printf "%s" "指定されたデバイスのデータは全て削除されます。よろしいですか？[y/n]："
read -r canExecute
canExecute="$(println "${canExecute}" | grep "^[Yy]$")"
[ -z "${canExecute}" ] && throwException "y以外が入力された為、処理を中止しました。"

dd bs=64k if="${isoFile}" of="${deviceFile}" && sync && println "書き込みが完了しました。"
```

# 後記
- コマンドのオプションや正規表現の書き方に違和感を覚えた方もいるかと思いますが、これは本シェルスクリプトを作成するにあたり可能な限りPOSIXに準拠したためです。具体的には以下の通りです。
    - 本シェルスクリプトで使われている構文はPOSIXに準拠しています。
    - 本シェルスクリプトで使われているコマンドおよびそのオプションについて、POSIXに規定されているコマンドについてはオプションも含めてPOSIXに準拠しています。
    - 本シェルスクリプトで使われている正規表現はPOSIXのBRE(Basic Regular Expression)に準拠しています。
- 本記事および本シェルスクリプトへの指摘・疑問はいつでも歓迎します。ただし、必ず返信するとは限りません。