シェルスクリプト浅めの深堀り

0.9K Views

April 14, 23

スライド概要

シェルスクリプト(主にbash)の内容を浅く見つつ、少し深めの内容まで触れました。

profile-image

LIFULL HOME'Sを運営する株式会社LIFULLのアカウントです。 LIFULLが主催するエンジニア向けイベント「Ltech」等で公開されたスライド等をこちらで共有しております。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

シェルスクリプト 浅めの深堀り 株式会社LIFULL 社内技術勉強会 プラットフォームG 寺井輝 1 1 © LIFULL Co.,Ltd. 本書の無断転載、複製を固く禁じます。

2.

今日の内容 - Shebang の話 - setでオプションをつけられる話 - [ ] と [[ ]] の話 - ファイルディスクリプタの話 ※ 以降のシェルスクリプトは全てbashで動かすことを想定しています 2

3.

Shebang の話 Shebang(しばん、しぇばん) はシェルスクリプト一行目に書くおまじない Shebangに書いた処理系の実行ファイルで実行される bashなスクリプトだと以下のパターンが多い #!/bin/bash or #!/usr/bin/env bash これらは何が違うのか、どちらがいいのか問題 3

4.

/bin/bash と /usr/bin/env bash #!/bin/bash - /bin/bash で実行 ほとんどのUNIXベースのOSではデフォルトの場所 必ず指定した実行ファイルを使うので比較的セキュリティが高い(らしい) 追加でパラメータを渡すことが可能 システムによっては実行ファイルの場所が違って動かない場合もある #!/usr/bin/env bash - $PATH を参照して実行可能なファイルを検索して実行 システムやユーザーごとに設定に柔軟に対応できる 移植性が高いことがメリット 追加でパラメータを渡すことができない 実行環境の $PATH によるため意図しない動きが出る可能性はある (確実に /bin/bash が動く開発環境なら #!/bin/bash でいいんじゃないかな) 4

5.

Shebang の動きをもう少し深堀り > Shebangに書いた処理系の実行ファイルで実行される ということはつまり処理系実行ファイルならなんでも動かせたりします test.sh test.sh #!/bin/echo #!/bin/cat $ ./test.sh hoge > hoge $ ./test.sh hoge.md > hoge.mdの中身 5

6.

Shebang に追加パラメータを設定する #!/bin/bash について > 追加でパラメータを渡すことが可能 #!/bin/bash -x (デバッグ行が出力されるオプション) ただし、明示的に実行ファイルを指定された時には消えてしまうことに注意 test.sh #!/bin/bash -x $ bash test.sh > (xオプションが反映されない) -> bash なオプションを設定する時は set を使った方がよいかも 6

7.

set で実行時のオプションをつけられる話 よく見るsetオプション四天王 -e : エラーがあったら終了 -x : デバッグ行っぽいのを出力 -u : 未定義変数がある時にエラー -o pipefail: -oはシェルオプションの設定、pipefailだとパイプの左辺失敗時にエラー set -euxo pipefail ↑ こんな感じで一括で書ける 7

8.

set は途中で変更できます setオプションの - は実は マイナス のことなので +(プラス) もあります #!/bin/bash set -x echo ‘hoge’ set +x echo ‘hogehoge’ $ ./test.sh + echo hoge hoge + set +x hogehoge 一部だけxオプションで囲ってデバッグなど可能 8

9.

[ ] と [[ ]] の話 [] - コマンドの test と同等 [[ ]] - bash で拡張されたキーワード - 拡張正規表現が使えたりパラメータ展開ができる - && , || , < , > も使える 先に結論: 実行がbash前提であれば [[ ]] を使っておけば間違いはない 9

10.

[ ] と [[ ]] をもう少し詳しく見る 正規表現が使える #!/bin/bash x=1223 [ $x =~ ^12*3$ ] && echo ok [[ $x =~ ^12*3$ ]] && echo ok $ ./test.sh ./test.sh: line 5: [: =~: binary operator expected ok ワイルドカードが使える #!/bin/bash x=1223 [ $x == 1*3 ] && echo 'single ok' [[ $x == 1*3 ]] && echo 'double ok' $ ./test.sh double ok 10

11.

[ ] と [[ ]] をもう少し詳しく見る 2 -n の挙動が微妙に違う #!/bin/bash x=1 y='' [ -n $x ] && echo 'single not empty1' [[ -n $x ]] && echo 'double not empty1' [ -n “$y” ] && echo 'single not empty2' [[ -n $y ]] && echo 'double not empty2' [ -n “$z” ] && echo 'single not empty3' [[ -n $z ]] && echo 'double not empty3' $ ./test.sh single not empty1 double not empty1 single not empty2 single not empty3 11

12.

ファイルディスクリプタ の話 ファイルディスクリプタ: ファイル書き込み用の通信チャネルのようなもの 一般に以下のようになっている (man bash すると詳しく出てくる) ファイルディスクリプタ番号 出力先 0 標準入力 1 標準出力 2 標準エラー出力 n 任意の出力先 備考 OSで標準的に番号付けされている 自分で設定可能 つまり...? $ echo ‘hoge’ > hoge は標準出力(ファイルディスクリプタ1)を使ってhogeに出力 $ echo ‘hoge’ 2> hoge は標準エラー出力(ファイルディスクリプタ2)を使ってhogeに出力 12

13.
[beta]
ファイルディスクリプタが競合する問題を考える
Q. in から一行ずつ読み込んで、それぞれのループで出力するか否かをインタラクティブにやりたい
test.sh
#!/bin/bash
while read -r line; do
read -p "表示する? (y/N): " ans
case "$ans" in
[yY]*) echo $ans;;
*);;
esac
done < <(cat ./in)

in

<

in1
in2
in3
in4

どんな挙動になる?

13

14.
[beta]
ファイルディスクリプタが競合する問題を考える 2
A. 何も表示されません
$ ./test.sh
$

もう少し詳しく見る

test.sh

#!/bin/bash
while read -r line; do
read -p "表示する? (y/N): " ans
echo "line: $line"
echo "ans: $ans"
case "$ans" in
[yY]*) echo $ans;;
*);;
esac
done < <(cat ./in)

$ ./test.sh
line: in1
ans: in2
line: in3
ans: in4

$line と $ans の両方に標準入力の値が入ってしまっている
ファイルディスクリプタ0が競合している状態
14

15.
[beta]
ファイルディスクリプタが競合する問題を考える 解決案
標準入力用のファイルディスクリプタが被っているので、片方を別のものに指定すればよい
test.sh
#!/bin/bash
while read -r line <&3; do
read -p "表示する? (y/N): " ans
echo "line: $line"
echo "ans: $ans"
case "$ans" in
[yY]*) echo $line;;
*);;
esac
done 3< <(cat ./in)

$ ./test.sh
表示する? (y/N):
line: in1
ans: y
in1
表示する? (y/N):
line: in2
ans: y
in2
表示する? (y/N):
line: in3
ans: y
in3
表示する? (y/N):
line: in4
ans: y
in4

y

y

y

y

15

16.
[beta]
おまけ: その他 ネタ
bashとzshだと配列のindexがずれるので注意
#!/bin/bash
arr=(a b c)
for i in {0..3}; do
echo "arr[$i] = ${arr[$i]}"
done;

#!/bin/zsh
arr=(a b c)
for i in {0..3}; do
echo "arr[$i] = ${arr[$i]}"
done;
#!/usr/local/bin/fish

ちなみにfishも1からだった

set arr a b c
for i in (seq 0 3)
echo "arr[$i] = $arr[$i]"
end

$ ./test.sh
arr[0] = a
arr[1] = b
arr[2] = c
arr[3] =

$ ./test.sh
arr[0] =
arr[1] = a
arr[2] = b
arr[3] = c

$ ./test.sh
./test.sh (line 6): array indices start at 1, not 0.
echo "arr[$i] = $arr[$i]"
^
arr[1] = a
arr[2] = b
arr[3] = c

16

17.

まとめ - 実行環境が固定の場合は適切な記載があることが多い - ファイルディスクリプタの概念 - 特に標準入出力の扱いに注意 17

18.

参考リンク Shebang関連 https://www.baeldung.com/linux/bash-shebang-lines 括弧表記の違いについて https://genzouw.com/entry/2020/06/09/083836/2041/ ファイルディスクリプタ関連 https://qiita.com/laikuaut/items/e1cc312ffc7ec2c872fc https://milestone-of-se.nesuke.com/sv-basic/linux-basic/fd-stdinout-pipe-redirect/ 18