Corredor

ウェブ、プログラミングの勉強メモ。

シェルスクリプトで開発案件ディレクトリの雛形とプレースホルダを作る

以下のサイトで紹介されている、Windows バッチでよくお世話になったスクリプトを Bash で作り直してみた。

language-and-engineering.hatenablog.jp

サブルーチンを用いた実装になっているが、やっていることをものすごく単純化すると、

Mkdir 01_概要設計
Mkdir 02_基本設計
……

Cd 01_概要設計
Type Nul > TODO_概要設計書を作る
Cd ..\

Cd 02_基本設計
Type Nul > TODO_画面仕様書を作る
Cd ..\

ってなことをやっているバッチファイルだ。

コレをシェルスクリプトに移植してみた。

#!/bin/sh

# +-----------------------------------------------------------
# | 開発案件ディレクトリの雛形を作成する
# | 
# | 本スクリプトを実行したカレントディレクトリ配下に、
# | 工程別のディレクトリおよびサブディレクトリの他、
# | ディレクトリの使い方を示すプレースホルダを作成する。
# +-----------------------------------------------------------

echo 開発案件ディレクトリの雛形を作成します。
read -p "実行してよろしいですか? (y/n) :" YN

if [ ! "${YN}" = "y" ]; then
  echo "中止します。"
  exit 1;
fi

mkdir -p \
01_プロジェクト計画 \
02_概要設計 \
03_基本設計/01_画面仕様書 \
03_基本設計/02_データモデル \
04_実装 \
05_総合テスト/01_テスト計画書 \
05_総合テスト/02_テスト仕様書 \
05_総合テスト/03_テスト証跡 \
05_総合テスト/04_テスト結果報告書 \
06_議事録 \
07_リリース \
99_その他

touch \
01_プロジェクト計画/TODO:プロジェクト計画書 \
02_概要設計/TODO:概要設計書 \
03_基本設計/01_画面仕様書/TODO:画面遷移図・画面仕様書 \
03_基本設計/02_データモデル/TODO:テーブル定義書・ER図 \
04_実装/00_MEMO:ソースはGitで管理・実装時の調査資料などを格納する \
06_議事録/00_MEMO:「YYYYMMDD_会議名」の形式でディレクトリを作成する

find . | sort

echo 開発案件ディレクトリの雛形が完了しました。

このスクリプトを create-templates.sh とかいう名前で保存しておいて、以下のように実行する。

$ sh ./create-templates.sh

実行確認は read コマンドで。mkdir -p でディレクトリ、touch でプレースホルダとなる空ファイルを作るワケだが、引数を複数渡せばその分ディレクトリやファイルを作ってくれる。\ でエスケープするとコマンドを改行できるので、まとめて作ってしまおう。

ディレクトリの作成結果は tree の代わりに findsort で表示。

シェルスクリプト自身を削除させる処理は今回移植しなかった。というのも、シェルスクリプトはどこかに置いておき、カレントディレクトリさえ変えて実行すれば、使い回しができるからだ。

$ cd ~/path/to/work/dir/
$ sh ~/create-templates.sh

Windows バッチを書くぐらいなら、シェルスクリプトの方がもう少し柔軟で高機能かなぁ。

シェルプログラミング実用テクニック (Software Design plus)

シェルプログラミング実用テクニック (Software Design plus)

Bash シェルスクリプトを安全に実行するための便利な set コマンド

Jenkins で実行するシェルスクリプトを書いていて、

  1. sh ./script.sh で実行すると実行されるコマンドが Jenkins のコンソールに出力されないから分かりづらいな
  2. コーディングミスとかでエラーになった時は中断してほしいな
  3. コーディングミスで未定義の変数を使ってたりしたら教えてほしいな
  4. というか実行前に Lint チェックみたいなことできなのかな

と思ったので調べてみたところ、set コマンドの各種オプションがそれぞれの要望を叶えてくれることが分かった。

そこで今回は、set コマンドの便利オプションと使い方を紹介する。

set -x : シェルスクリプト内のコマンドをコンソール出力する

まず set -x から。コレをシェルスクリプトの先頭に書いておく。

#!/bin/bash

set -x

echo ほげふが

この状態で $ sh コマンドを叩くと、以下のように出力される。

$ sh script.sh
+ echo $'?\201??\201\222?\201??\201\214'
ほげふが

日本語部分は数値参照になったりするが、だいたい何のコマンドを叩いたか分かるので助かる。

set -v : 実行するコマンドそのものを出力する

set -x に似たオプションが、set -v。こちらのオプションは、変数などを展開せず出力してくれる。

#!/bin/bash

set -v

MY_VARIABLE='Hoge'
echo $MY_VARIABLE

こんなファイルがあったとして、次のように出力される。

$ sh script.sh

MY_VARIABLE='Hoge'
echo $MY_VARIABLE
Hoge

echo $MY_VARIABLE 部分が分かりやすいだろう。変数展開せず、コマンドをそのまま出力している。

シェルスクリプトの set -v 部分を set -xv もしくは set -vx (順不同) とすることで、set -x オプションとの併用もできる。

$ sh script.sh

MY_VARIABLE='Hoge'
+ MY_VARIABLE=Hoge
echo $MY_VARIABLE
+ echo Hoge
Hoge

+ から始まる行が set -x によるモノ。set -v によるコマンド出力なのか、コマンド実行結果の標準出力なのかが一見分かりづらいものの、充分にデバッグできるだろう。

set -e : エラー時にシェルスクリプトを中断する

お次は set -e。まずはこのオプションを指定せず、以下のようなシェルスクリプトを用意してみる。

#!/bin/bash

cat none.txt
echo ほげふが

ココで、cat で出力しようとしている none.txt というファイルが存在しないとする。通常であれば、以下のような実行結果になる。

$ sh script.sh
cat: none.txt: No such file or directory
ほげふが

cat コマンドが正常に動かず、その後に書かれた echo コマンドも実行される。

ココに set -e コマンドを追加してみよう。

#!/bin/bash

set -e

cat none.txt
echo ほげふが

そしてシェルスクリプトを実行してみると、以下のようになる。

$ sh script.sh
cat: none.txt: No such file or directory

cat でエラーが出ると、後続の echo コマンドが実行されていないことが分かる。シェルスクリプトがエラーの時点で中断されているのだ。

もし、エラーが出る場合が想定済みで、エラーを無視したい場合は、次のように || true とすれば誤魔化せる。

cat none.txt || true

また、-e オプションを一時的に無効化することもできる。コレについては後述。

set -u : 未定義の変数が使用された時にシェルスクリプトを中断する

今度は set -u。コチラは未定義の変数を使用した時にシェルスクリプトを中断してくれる。

#!/bin/bash

echo ほげ
echo $NONE_VARIABLE
echo ふが

例えばこんなコードがあり、$NONE_VARIABLE という変数が未定義だとする。このままだと、

$ sh script.sh
ほげ

ふが

というように、未定義の $NONE_VARIABLE を空文字のように扱い、処理が継続してしまう。

コレに対し、

#!/bin/bash

set -u

echo ほげ
echo $NONE_VARIABLE
echo ふが

set -u コマンドを設定すると、以下のようになる。

$ sh script.sh
ほげ
script.sh: line 6: NONE_VARIABLE: unbound variable

最後の echo ふが は実行されておらず、処理がそこで中断されていることが分かる。

set -n : コマンドを実行せず構文チェックのみを行う

シェルスクリプトの挙動を変更するオプションとしては最後に紹介するモノ。set -n は、シェルスクリプト内のコマンドは実行せず、構文チェックのみ行ってくれる。

例えば以下のような、if コマンドの書き方が間違っているスクリプトを用意する。

#!/bin/bash

set -n

echo スタート
if[true]; then
  echo HOGE
fi
echo おわり

シェルスクリプトはコマンドの羅列であり、if[] はそれぞれ別のコマンドであるから、本来はスペースで区切らないといけないのだが、うっかりスペースを入れずに繋げて書いてしまっている。

コレを実行してみると、

$ sh script.sh
script.sh: line 6: syntax error near unexpected token `then'
script.sh: line 6: `if[true]; then'

と、このように echo などコマンドは一切実行されず、構文チェックのみ行われている。

コーディング中はこのオプションを付与して構文誤りがないかチェックし、実装が完了したら set -n コマンドを除去して実際に動作するようにしておこう。

set -o : set コマンドでの設定状況を知る

ココからは set コマンドの活用編。ターミナルで $ set -o と叩くと、これら set コマンドのどのオプションが有効・無効になっているか確認できる。こんな感じだ。

$ set -o
allexport       off
braceexpand     on
emacs           on
errexit         off
errtrace        off
functrace       off
hashall         on
histexpand      on
history           on
ignoreeof       off
interactive-comments    on
keyword         off
monitor         on
noclobber       off
noexec          off
noglob          off
nolog           off
notify          off
nounset         off
onecmd          off
physical        off
pipefail        off
posix           off
privileged      off
verbose         off
vi              off
xtrace          off

set + : 設定したオプションを無効に戻す

ココまで、set -vset -e のように、ハイフン - でオプションを指定して、そのオプションを有効にしてきたが、これらのオプションを一時的に無効にしたい場合もあるだろう。

その際は、set +v とか set +o のように、プラス + 記号でオプションを指定すると、そのオプションを無効化できる。

#!/bin/bash

# エラー時に中断するようにする
set -e

echo ほげほげ

# 以下のコマンドでエラーが起きても、ココだけは無視して継続させたい
# そんな時は set +e でオプションを解除する
set +e

cat none.txt

# また元に戻す
set -e

echo おわり

こんな感じ。

当然だが、set コマンドはシェルスクリプト内でなくとも、ターミナル上でも使用できるので、先程の set -o オプションを使って設定状況を見比べてみよう。

# 設定確認
$ set -o | grep xtrace
xtrace          off       # off の状態

# x オプションを設定する (set -o xtrace と書いても良い)
$ set -x
++ update_terminal_cwd
++ local url_path=
++ local i ch hexch LC_CTYPE=C LC_ALL=
# 省略……

# 設定確認
$ set -o | grep xtrace
+ set -o
+ grep --color xtrace
xtrace          on       # on になっている
++ update_terminal_cwd
++ local url_path=
++ local i ch hexch LC_CTYPE=C LC_ALL=
# 省略……

# x オプションを切る (set +o xtrace と書いても良い)
$ set +x
+ set +x

$ set -o | grep xtrace
xtrace          off       # off に戻っている

このように、オプションの On・Off ができている。

Shebang でセットする書き方がうまくいかなかった

この set コマンドと同等のオプションを、Shebang のところに書いても良いかのように読み取れる文献もあったのだが、自分が試した限りだと上手くいかなかった。

#!/bin/bash -xe

# ↑ こんな風に設定できるらしいが、有効にならず。

仕方がないので、自分は

#!/bin/bash
set -xe

と、Shebang の直後に set コマンドを書く運用にする。

まとめ

  • 実装中に構文チェックしたい (コマンドは実行されない)
    • set -n
  • スクリプト実行時に表示されるコマンドを見たい
    • set -x (行頭に + 記号が付き、変数展開される)
    • set -v (変数展開されない)
  • スクリプト実行時にエラーがあったらその時点で中断したい
    • set -e (終了コードが 0 でないコマンドが出たら中断する)
    • set -u (未定義の変数が出てきたら中断する)
  • 個人的によく使う組み合わせ
    • set -xeu

以上。set コマンドでシェルスクリプトがとても安全・便利に書けるようになった。

新しいシェルプログラミングの教科書

新しいシェルプログラミングの教科書

Jenkins の実行結果を UNSTABLE (不安定) にする Text-Finder Plugin

Jenkins ジョブでシェルスクリプトを実行し、その結果に応じて「成功 (SUCCESS)」「失敗 (FAILURE)」に振り分けようと思ったのだが、「失敗 (FAILURE)」はその中身が分かりにくい。つまり、

  • 実行したシェルスクリプトに構文エラーがあって失敗したのか、
  • curl した先のサーバが死んでいて通信エラーになって失敗したのか、
  • grep で何もヒットしないことにより異常終了コードが吐かれる」ことが想定済みのパターンであり、意図的に失敗させているのか、

といったバリエーションの区別が付かないのだ。

そこで、最後の「意図的に失敗させる」場合を、「UNSTABLE (不安定)」というステータスで終了させ、ジョブの実行結果を「成功」「失敗」「不安定 (UNSTABLE)」の3種類で表現できるようにしようと思う。

Text-finder Plugin

ジョブの実行結果を強制的に「不安定 (UNSTABLE)」に変えるため、Text-finder Plugin という Jenkins プラグインを導入する。

コレをインストールすると、ジョブの設定画面「ビルド後の処理」の中に、「Jenkins 文字列検索」という項目が表示される。

コレを追加し、以下のように設定する。

f:id:neos21:20180806235215p:plain

  • 検索するファイル:(空白)
  • コンソール出力も検索する:チェックする
  • 正規表現:「UNSTABLE!」 ← ココではこのように設定しておく。
  • 検出時に成功扱いにする:チェックしない
  • 検出時に不安定扱いにする:チェックする

一方、ジョブとして実行するシェルスクリプトの方は、以下のように実装する。

# 異常終了することを想定しているので、ジョブの実行結果を「UNSTABLE」に見なしたい処理
# ココでは、$MY_FILE というテキストファイル内に、指定の文言「WARNINGS FOUND」があった場合は、
# シェルスクリプトとしては正常終了させるが、ジョブの実行結果としては「UNSTABLE」にしたい、とする。

if [ "`grep 'WARNINGS FOUND' $MY_FILE`" ]; then
  echo 'UNSTABLE! : 警告が見つかりました'
  exit 0
fi

UNSTABLE と見なしたいところで、echo UNSTABLE! と実行し、Jenkins ジョブのコンソール出力に「UNSTABLE!」という文言を表示させている。

exit 0 としているので、シェルスクリプトとしては正常終了、本来ならジョブは「成功」扱いになるのだが、先程設定した「Jenkins 文字列検索」(Text-finder Plugin) により、コンソール出力の中から「UNSTABLE!」という文言を見付けて、「不安定 (UNSTABLE)」扱いにしてくれる、というワケだ。

今回は Jenkins ジョブ内でシェルスクリプトを実行するようにしたが、シェルスクリプトに限らず別の方法でコンソール出力できれば、それを対象に「正規表現」で検索できる。それに、「検索するファイル」項目があるとおり、何らかのファイルの中から任意の文字列を探しても良い。

柔軟に使えると思うので、ぜひ Text-finder Plugin を導入して試してみてほしい。

チーム開発実践入門 ~共同作業を円滑に行うツール・メソッド (WEB+DB PRESS plus)

チーム開発実践入門 ~共同作業を円滑に行うツール・メソッド (WEB+DB PRESS plus)

sed で行追加する i オプション (と、MacOS の BSD sed での注意点)

sed で行を追加する、i というコマンドを使ってみる。

今回はサンプルとして、cat にヒアドキュメント << を使って複数行のテキストを用意する。コレをパイプで sed に渡し、1行目にテキストを追加してみる。

# まずは普通に出力してみる
$ cat << EOM
> Original
> Text
> EOM
Original
Text

# ↑2行出力される

# 次に sed で行追加してみる
$ cat << EOM | sed -e '1iInserted'
> Original
> Text
> EOM
Inserted
Original
Text

# ↑1行目に「Inserted」と書かれ、3行になる

ファイルを読み込む場合はこのように。

$ cat my-text.txt
Original
File

$ sed -e '1iInserted' my-text.txt
Inserted
Original
File

ファイルに上書きする場合は -i オプションを付け足す。

$ sed -e '1iInserted' -i my-text.txt

-e オプションの方は、「行追加したい行」 + i + 追加したい文字列という書式で書く。だから、3行目に追加したい場合は -e '3iほげほげ' という風になる。

コレは GNU sed の話

ココまで紹介してきた i コマンドは、GNU sed のお話。つまり Linux の sed や、Windows GitBash に同梱される sed の場合だ。MacOS に搭載されている BSD sed で同じコマンドを叩くと、次のようなエラーが出る。

# MacOS 標準の BSD sed を使ってみると…
$ cat << EOM | sed -e '1iInserted'
> Original
> Text
> EOM
sed: 1: "1iInserted
": command i expects \ followed by text

# このように「command i expects \ followed by text」エラーが出る

回避方法はあるようだが、面倒くさい。

だから自分は、以前紹介したように Homebrew で gnu-sed (gsed コマンド) を導入し、コチラを使うようにしている。

grep,sed,awk

grep,sed,awk

『中古』grep,sed,awk

『中古』grep,sed,awk