この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。
前回はシェルスクリプトの始まりの部分とディレクトリパスの確認を行いました。
今回はif文と引数、変数を利用していきます。
if文を書いてみる
---checkVariable.sh
#!bin/bash
if [ "$1" == "" ]; then
echo "引数が設定されていません。"
else
echo "引数は$1です。"
fi
if文を使う場合はifで始まり、fiでif文全体の終わりを示します。
(条件分岐にはcase文もありますが使用頻度は……)
---listVariables.sh
#!bin/bash
if [ $# == 0 ]; then
echo "引数が設定されていません。"
else
for variable in $@
do
echo "引数の1つ:$variable"
done
fi
入力した引数を順々にvariable
に格納し、表示していきます。ただし、引数を設定していない場合は「設定されていない」旨のメッセージを表示します。
さて、$#
や$@
、$1
といった変数は、シェル実行時点で初期値が決まっています。入る値は以下の通り。
$#
:実行するシェルファイル名の後に続く引数の数(半角スペース区切り)$@
:実行するシェルファイル名の後に続く引数全体$1
:実行するシェルファイル名の後に続く引数の1つ目。2つ目以降は$2
、$3
・・・となる。
これがおおよそわかっていれば、引数の順序について規則を設けた上で対応した変数を利用することができます。
少し本格的に書いてみる
set -eu
を利用していますが、こちらを参考に。(コメントも大事なことが。)
シェルスクリプトを書くときはset -euしておく
--- copyFile.sh
#!bin/bash
#bash記述のため、cd等実行エラー時強制終了するため保護
#想定外の事故をなるべく回避するため記載
set -eu
if [ $# != 2 ]; then
echo "引数不足、または引数過多です。コピー前とコピー後のファイル名を入力してください。"
exit
fi
#シェル自身のディレクトリを格納する。
dirPath=$(dirname $(readlink -f $0))
#半角スペースが含まれるディレクトリの場合は""でくくることで引数の区切りとして認識されなくなる
cd "${dirPath}"
if [ -e "$1" ] && [ ! -e "$2" ]; then
cp -a "${dirPath}/$1" "${dirPath}/$2"
echo "${dirPath}/$1を${dirPath}/$2にコピーしました。"
elif [ ! -e "${dirPath}/$1" ]; then
echo "コピー元のファイルがありません。"
else
echo "コピー先のファイルが既に存在します。上書きを防ぐため、実行を中止しました。"
fi
引数が2つぴったりでないと実行しない、ファイルをコピーするシェルスクリプトです。引数が2つ以外の時は、最初のif文で警告文を表示し、その後exit
で実行を終了します。
処理するファイルの基準はシェルの置いてあるディレクトリとします。そのためシェルのディレクトリ位置を取得し、 cd
コマンドで移動しています。
2つ目のif文ではコピー元のファイルが存在すること、及びコピー先に指定した名前のファイルが存在しないことを確認しています。分岐が3つになったので、else if
にあたるelif
を使用しています。
変数記述に注意
引数を順々に表示するシェルと違い、自身で設定した変数(dirPath
など)に{ }
が付いています。このスクリプトでは{ }
はなくとも正常に動作するのですが、例えば、
--- wrongCopy.sh
#!bin/bash
# -u オプションは後解説用にわざと外しています
set -e
dirPath=$(dirname $(readlink -f $0))
cd "${dirPath}"
if [ -e "$1.txt" ] && [ ! -e "test_$2.txt" ]; then
echo "===== コピー開始 ====="
#一度$2をtargetに格納する
target=$2
target_today=wrong
#ここだけ外す
cp -a "$1.txt" "test_$target_today.txt"
echo "===== コピー終了 ====="
echo "test_${target}_today.txt が作成されました。"
ls -la test_*
fi
拡張子前の名前を引数として、これを実行した際に以下のようになります。
想定と違うファイルが生成されましたね。
これはtarget
という変数ではなく、target_today
という変数で展開されているためです。一方で、先に出した例では/
が存在するために変数名がその手前で終了していると認識されたため、正常に動作してしまいます。$1
や$2
なども同様の現象に陥りそうですが、こちらは元々ユーザ側では先頭が数字の変数名をつけられないため、別に認識してくれるようです。
正常に動作するパターン、正常に動作しないパターンを覚えてもいいですが、それよりも変数名を{ }
でくくるようにした方が予期せぬ動作を起こしにくくなると思います。
また、今回target_today
の値を設定していますが、未定義の場合はtest_.txtとして生成されます。(未定義の場合にも終了させるset -u
オプションがあります。)
存在確認前に引数の例外処理を実行
少し判定文を付け加えます。
--- copyFile.shに追加
〜省略〜
dirPath=$(dirname $(readlink -f $0))
# /と/homeだけ弾く例
if [[ "${dirPath}" =~ ^/(home/*)?$ ]]; then
echo "現在置かれているシェルの場所では実行できません。実行するユーザのディレクトリ以下の場所に配置してください。"
exit
else
echo "${dirPath}"
fi
if [[ "$1" =~ "\.\." ]] || [[ "$2" =~ "\.\." ]]; then
echo "予期しない動作を行う可能性があるため、処理を終了します。"
exit
fi
cd "${dirPath}"
if [ -e "$1" ] && [ ! -e "$2" ]; then
〜省略〜
cpコマンド前に終了したので生成されない
実行したくないディレクトリなのかを確認
簡易的ですが、このディレクトリでは絶対に処理させたくないことを示すif文を最初に記載しています。前回にも書きましたが、テスト用の環境構築を行ってから実行しましょう。2つ目は1つ上のディレクトリに行く記述を禁止しています。
また、判定文として正規表現を利用しています。[
の数が変わる等、変化しているので注意してください。
終わりに
前回締めに書いた通り、今回はif文と変数を利用した、上書きできないコピーを行うシェルを紹介しました。正規表現もパスの確認のために少し利用しました。これくらいの内容であれば、普通にコマンド入力した方がはやいのですが、様々な処理を盛り込んだり、一部同じ値を使いまわしたりする場合は、単純なコピーでも作成すると楽になるのではないでしょうか。
記事を書いている本人もしっかりしたものを書けるわけではないですが、興味や参考になればと思います。