#author("2021-01-03T12:46:52+00:00","default:src128","src128")
#author("2022-01-18T11:07:39+00:00","default:src128","src128")
&tag(Bash);
*目次 [#k301f9e1]
#contents
*参考情報 [#v39c8200]
-[[OKLab - Bourneシェルスクリプト入門(+bash):http://www.oklab.org/program/sh.html]]
-[[bash 入門:http://www.hpc.cs.ehime-u.ac.jp/~aman/linux/bash/]]

*規約に関して [#ldaba577]
-[[シェルスクリプト Tips - UNIX & Linux コマンド・シェルスクリプト リファレンス:http://shellscript.sunone.me/tips.html]]。ファイル名の命名方法とか参考になる。
-[[シェルスクリプトのコーディングスタイル・コーディング規約について考えてみる - 双六工場日誌:http://sechiro.hatenablog.com/entry/2013/05/12/%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%83%BB%E3%82%B3%E3%83%BC]]
-[[ShellScript - シェルスクリプトを書くときに気をつける9箇条 - Qiita:http://qiita.com/b4b4r07/items/9ea50f9ff94973c99ebe]]

**変数名の規約 [#rb2c3908]
-ローカル変数: 小文字、グローバル変数: 大文字、エクスポートする変数: 大文字。
-インデントはスペース4個。
-readonlyはできれば使う。exportと併用する場合、readonlyで定義してそのあとexportすれば良い。
-ファイル名の名前だけFOO_FILENAME、フルパスFOO_FILE。ディレクトリ名の名前だけFOO_DIRNAME、フルパスFOO_DIR

**★デフォルトオプション [#o0d24102]
-かならずset -euする。set -eでエラーで止まる。set -uで未定義変数でエラー。[[Bash - シェルスクリプトを書くときはset -euしておく - Qiita:http://qiita.com/youcune/items/fcfb4ad3d7c1edf9dc96]]
-set -uするとコマンドライン引数が指定されなかったときに、$1がエラーになる。これを防ぐためにはデフォルト値を使う。
 TASKNAME=${1:-abc}
-set -xするとコマンドが標準出力される
-set -euxがいいかも。

*パラメータ [#y79b6f9d]
,$0,コマンド名
,$1,1つ目の引数
,$2,2つ目の引数
,$*,残りの引数全部
,$# ,引数の個数

*変数 [#id0375d1]
**参考 [#oe39a741]
-[[bash シェルスクリプト -PG's PocketArms:http://himana.natsu.gs/bash-001.html]]

**基本 [#u13bee70]
-小文字でも大文字でも良い。
-宣言時は$を頭につけない。両端にスペースをいれない。
-参照時は$を頭につける。厳密に使いたい時は変数名を{}で囲う必要もある。
#pre{{
a="XYZ"
echo "$a"
echo "${a}"
}}
-空白がはいった変数のことを考えると、変数使用時常にダブルクォートで囲ったほうが安全かも。if文でエラーにならないように。

**環境変数とシェル変数 [#x1ba97f5]
-[[シェル変数と環境変数の違い - 燈明日記:http://d.hatena.ne.jp/chaichanPaPa/20090517/1242542427]]によると、ざっくり言ってローカル変数=シェル変数、グローバル変数=環境変数と考えて良い。
-別に大文字=環境変数というわけではない

***環境変数 [#ic5e748e]
-export FOOのようにexportされた変数が環境変数と呼ばれる。
-printenv FOOで確認できる。
-親シェルでexportされた変数は小シェルに引き継がれる。子で引き継がれた変数を変更しても親には影響しない。
-export済み変数を設定する場合再度exportする必要はない。例えばexport済みのLANGを変更する場合以下でOK。
 LANG="hoge"

***シェル変数 [#t55788e4]
-そのスクリプトか、または現プロンプト上でしかアクセスが有効でない。
-シェルスクリプトでローカル変数として使えるほか、bashのPS1のような変数にも使われている。
-set PS1のように確認できる。
- set で全部表示できる。
**デフォルト値 [#nf01d82c]
-${var:-デフォルト値}で設定できる。
#pre{{
var="a b c"
str=${var:-"d e f"}
echo $str
str=${var2:-"d e f"}
echo $str
}}
-以下が出力される
#pre{{
a b c
d e f
}}
-コマンド引数
-以下のシェルスクリプト default.sh を考える。
#pre{{
arg1=${1:-"xxx"}
echo $arg1
}}
-そのまま実行すると、xxxが出力される。「./default.sh yyy」と実行するとyyyが出力される。
*制御構造 [#k5839b22]
**if then else [#h8d19587]
-[[test と [ と [[ コマンドの違い - 拡張 POSIX シェルスクリプト Advent Calendar 2013 - ダメ出し Blog:http://fumiyas.github.io/2013/12/15/test.sh-advent-calendar.html]]にあるようにまじめに考えるとかなりめんどくさい。"[["は後から追加されたらしい。
-文字列の比較。"["の両方にスペース。"="の両方にスペースが必要。thenを続ける場合、"]"のあとに";"が必要。
-"-o"は"or"
#pre{{
if [
str="a b c d e"
if [ "$str" = "a b c" -o "$str" = "a b c d" ]; then
        echo ok
else
        echo ng
fi
}}
-否定の場合は != を使う。
*** 空白チェックする [#kf90a7c8]
- 直接""と比較するのが簡単か
#pre{{
    str=$1
    if [ "$str" = "" ]; then
        echo "empty"
    else
        echo "not empty"
    fi
}}


***ファイルの存在チェック [#rfcbf4d4]
-"-f"を使用する
#pre{{
if [ -f $file ]; then
    echo "$file is file."
else
    echo "$file is not file."
fi
}}
- びっくりマークで否定
#pre{{
if [ ! -f $file ]; then
    echo "$file is not file."
else
    echo "$file is file."
fi
}}


**exit [#jc5ec04c]
-終了正常の場合はexit 0。それ以外はエラー。[[コマンドラインツールを書くなら知っておきたい Bash の 予約済み Exit Code - Qiita:https://qiita.com/Linda_pp/items/1104d2d9a263b60e104b]]。
 exit 0
 exit 1
-呼び出し側では一時変数に保存してからチェックするのが無難っぽい。[[[ShellScript] $?をif-elifで使ったら死ぬよ!! · DQNEO起業日記:http://dqn.sakusakutto.jp/2013/10/shellscript_elif.html]]
ret=$?
if [ $ret -eq 0 ] ;then
    echo "ok"
else
   echo "ng"
fi

*関数 [#bb3d2895]
**パラメータの基本 [#uf134564]
-$1,$2のように格納される。そのまま使ってもいいし変数に代入してもよい(厳密にはstrとするとグローバル変数になるのでローカルにしたほうがよい)。
#pre{{
function test()
{
    str=$1
    echo "$str"
}

test a
}}

**パラメータの存在チェック [#a9cdb75e]
-以下のようなスクリプトを考える。
#pre{{
function check()
{
    str=$1
    if [ "$str" = "" ]; then
        echo "empty"
    else
        echo "not empty"
    fi
}

echo "\$0=$0"
check $0

echo "\$1=$1"
check $1

echo "\$2=$2"
check $2
}}
-./param.shとして実行すると以下の結果が表示される。
#pre{{
$0=./param.sh
not empty
$1=
empty
$2=
empty
}}
**パラメータとして配列を含む引数を渡したい [#a0636306]
-ただ単に配列を渡す場合、[[逆引きシェルスクリプト/引数を配列に展開する方法 - Linuxと過ごす:http://linux.just4fun.biz/%E9%80%86%E5%BC%95%E3%81%8D%E3%82%B7%E3%82%A7%E3%83%AB%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88/%E5%BC%95%E6%95%B0%E3%82%92%E9%85%8D%E5%88%97%E3%81%AB%E5%B1%95%E9%96%8B%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95.html]]のように呼び出される側で次のように受ければ良い
 argv=("$@")
-配列のほかにも引数を渡したい場合工夫が必要となる。
***shift併用型 [#p084954d]
-最初の引数とそれ以外の配列引数に分ける。[[Passing arrays as parameters in bash - Stack Overflow:http://stackoverflow.com/questions/1063347/passing-arrays-as-parameters-in-bash]]
#pre{{
calling_function()
{
    variable="a"
    array=( "x", "y", "z" )
    called_function "${variable}" "${array[@]}"
}

called_function()
{
    local_variable="${1}"
    shift
    local_array=("${@}")
}
}}

***動的スコープでそのまま参照 [#q84ee051]
-bashの変数は動的スコープなので呼び出し先でそのまま参照できる。local指定しておけばグローバル変数とも区別できる。[[本を読む bashで関数に配列を渡す:http://emasaka.blog65.fc2.com/blog-entry-1223.html]]


*コマンド置換 [#d8049cf2]

**バッククォート`command`または$(command)を使う [#p874dd44]

*Tips [#g322c910]

**デバッグ方法 [#pce04bda]
-[[Debugging Bash scripts - Advanced Web Machinery:https://advancedweb.hu/debugging-bash-scripts/]]
**.bash_profile vs .bashrc [#v1e55411]
-通常はログイン時に.bash_profile、シェル起動時に.bashrcらしい
-OS XでGUIからターミナルを開いたときは、.bash_profileしか読み込まれない(X11からターミナルを開く場合は.bashrcが読まれるらしいので挙動が違う)。
-そこで、.bash_profileから.bashrcを読み込むように設定しておく。
#pre{{
# read .bashrc                                                                  
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
}}
-というよりもGUIから起動した場合.bashrcが読み込まれないということかも。Ubuntuでは[[EnvironmentVariables - Community Help Wiki:https://help.ubuntu.com/community/EnvironmentVariables]]、~/.profileを推奨しているようだ。
-ということで、環境変数は.profileに記述すればよさそうだが、そう単純ではない。Capistranoでデプロイする際、.profile、.bash_profileではなく、.bashrcが読み込まれる(Capistranoは、non-login、non-interactiveシェルとされているが、Ubuntuではそうなる。[[Better explain how to specify server-side environment variables · Issue #1884 · capistrano/capistrano:https://github.com/capistrano/capistrano/issues/1884]] )。
-というこでまとめると以下のようになるのか(2020/12/28(月))
--.bashrc: インタラクティブ用の設定とCapistranoで使用する環境変数。Capistrano用は先頭付近にかかないと途中で処理が終わるので注意。
--.profile: 環境変数

**スクリプトの存在するディレクトリを取得したい [#j053da4c]
-スクリプト内から他のスクリプトを呼び出したい場合など、スクリプトが存在するディレクトリが必要になる場合がある。
 export BASE_DIR=$(cd $(dirname $0);pwd)
**複雑なプログラムを作成したい [#s9c330b9]
-オープンソースを参考にする。
-Linuxの/etc/network-scriptsとか、tomcatの起動スクリプトとか。

**実行したプログラムをエコーしたい [#qc374345]
-[[bashスクリプトにおいて、 コマンドを実行しながら、そのコマン… - 人力検索はてな:http://q.hatena.ne.jp/1319616956]]にあるように -x オプションを試用する。
 #!/bin/sh -x
 set -x

**OSを判定したい [#b7bb53c4]
-[[zshとVimでOS判定 - shkh's blog:http://shkh.hatenablog.com/entry/2012/06/17/222936]]にあるように$OSTYPEが使える。
-バージョン番号が付いていたりそのたプリフィックスが付属していることがあるので、末尾のアスタリスクを忘れないように。
#pre{{
case ${OSTYPE} in
    darwin*)
        #ここにMac向けの設定
        ;;
    linux*)
        #ここにLinux向けの設定
        ;;
esac
}}

**パラメータの個数をチェックする [#o03c7e0d]
-例えばパラメータ無しの場合とパラメータありの場合で処理を変更したい場合
#pre{{
function check()
{
    str=$1
    if [ "$str" = "" ]; then
        echo "引数なし"
    else
        echo "引数あり"
    fi
}
}}

**空白を含むファイル名を正しく渡したい [#xdef4987]
-例えばシェルスクリプトから、rubyスクリプトを呼び出したい場合単にバックスラッシュでエスケープして渡しただけだと、分割したファイル名として扱われる
 #!/bin/sh
 ruby test.rb $@
-この場合全体をエスケープしてやればよい。[[Accessing bash command line args $@ vs $* - Stack Overflow:http://stackoverflow.com/questions/12314451/accessing-bash-command-line-args-vs]]
 ruby test.rb "$@"


**チルダの展開 [#ie68f1d9]
-[[シェルスクリプトでチルダを含む文字列をチルダ展開する - fohte.log:http://fohte.hateblo.jp/entry/2017/01/04/222137]]にあるように、文字列中のチルダはそのままでは展開されない。
-そのたにはチルダのかわりに$HOMEを使えば良い。

**スクリプトで.bashrcの変更を反映させる。 [#n62e3bef]
-スクリプト内でだけ有効でよいなら
 source ~/.bashrc
-スクリプト終了後も有効にしたいなら。
 exec $SHELL --login
*トラブルシューティング [#jc21d146]

**echo -nがきかない [#e7468494]
-[[少しハマったシェルスクリプト by yota.log:https://ie.u-ryukyu.ac.jp/e095708/archives/768]]にあるようにMacだとだめらしい。
** "$@" != ""がエラーになる [#s33a4e3b]
-[[シェルスクリプトでの$@の罠:http://rcmdnk.github.io/blog/2014/06/25/computer-bash/]]に書いてあること。
-以下のスクリプトを実行するとき、「./atmark.sh a」はいいけど、引数なしor2個以上の引数でエラーとなる。
#pre{{
#!/usr/bin/env bash
if [ "$@" != "" ];then
  echo "$@"
else
  echo empty
fi
}}
-これは"$@"が次のように展開されるため。
 "$1" "$2" "$3"
-つまり、if [ "$@" = "" ];thenが、if [ "" = "" ];thenとなったりするため。

トップ   編集 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS