その他
    ホーム 技術発信 DoRuby シェルスクリプトと戦う〜その2〜
    シェルスクリプトと戦う〜その2〜
     

    シェルスクリプトと戦う〜その2〜

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

    前回はシェルスクリプトの始まりの部分とディレクトリパスの確認を行いました。
    今回はif文と引数、変数を利用していきます。

    if文を書いてみる

    ---checkVariable.sh
    
    #!bin/bash
    if [ "$1" == "" ]; then
      echo "引数が設定されていません。"
    else
      echo "引数は$1です。"
    fi
    
    

     
    enter image description here

     
    if文を使う場合はifで始まり、fiでif文全体の終わりを示します。
    (条件分岐にはcase文もありますが使用頻度は……)

    ---listVariables.sh
    
    #!bin/bash
    if [ $# == 0 ]; then
      echo "引数が設定されていません。"
    else
      for variable in $@
      do
        echo "引数の1つ:$variable"
      done
    fi
    

     
    enter image description here
     
    入力した引数を順々に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
    
    

     
    enter image description here
     
    引数が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
    

     
    拡張子前の名前を引数として、これを実行した際に以下のようになります。
     
    enter image description here
     
    想定と違うファイルが生成されましたね。
    これは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
    〜省略〜
    
    

     
    enter image description here
    cpコマンド前に終了したので生成されない
     
    enter image description here
    実行したくないディレクトリなのかを確認
     

    簡易的ですが、このディレクトリでは絶対に処理させたくないことを示すif文を最初に記載しています。前回にも書きましたが、テスト用の環境構築を行ってから実行しましょう。2つ目は1つ上のディレクトリに行く記述を禁止しています。
    また、判定文として正規表現を利用しています。[の数が変わる等、変化しているので注意してください。
     

    終わりに

    前回締めに書いた通り、今回はif文と変数を利用した、上書きできないコピーを行うシェルを紹介しました。正規表現もパスの確認のために少し利用しました。これくらいの内容であれば、普通にコマンド入力した方がはやいのですが、様々な処理を盛り込んだり、一部同じ値を使いまわしたりする場合は、単純なコピーでも作成すると楽になるのではないでしょうか。
    記事を書いている本人もしっかりしたものを書けるわけではないですが、興味や参考になればと思います。
     


    関連記事:
    シェルスクリプトを書くときはset -euしておく
    シェルスクリプトと戦う〜その1〜