シェルスクリプトから逃げていましたが、いよいよ向き合わないと進まなくなったので勉強しました。 このページだけである程度わかるようになり、後からも参照できるよう1ページで簡潔ですが、できるだけ網羅しました。
tab、空白、文末
tab
- 制約がないところではどこでも使えます。 基本tabを半角4幅等と設定してインデントに使うのがベスト。
- 制約があるのは、a=1などタブを間に入れることはできません。
半角スペース
- タブ同様制約がないところではどこでも使えます。
- 式の途中で最低1個必要な場合でも、1個以上の複数半角スペースは全て無視されます。
全角スペース
- 全角の空白があった場合、実行はされますが、警告が表示されます。
- なのでできるだけ全角空白を入れない様に気をつけましょう。
- 以下の場合、3行目にあると表示されています。
- viやnanoでは表示されませんが、その行でカーソルを移動させていくとカーソルが全角サイズになるので削除しましょう。 VScodeは全角空白を表示してくれるので便利です。
$ ./test.sh ./test.sh: line 3: : command not found
文末
何も不要。『;』なども必要ない。
コメント
- 『#』 で1行コメント
- 複数行コメントは無い
- 行の途中からでもOK
- 全角日本語で書いても構わない
#!/bin/sh #この行からコメント func1(){ # 行の途中からでもコメント for a in "$@" do echo $a #ここもコメント done }
改行(複数行記述)
コマンドが長い場合、複数行に渡って記述するには『\』を使う。
$ a\ > =3 $ echo $\ > a 3
shebang(シェバン)
- シェルスクリプトファイルの1行目に書く記号『#!』の事
- 先頭に必ず必要。 記述なければ、実行されないか、異なった文法で実行されることもあるので実は重要。
- 各シェル(sh、bash、ash、dashなど)により表記が若干異なる。
$ echo $SHELL
で現在使用中のシェルを確認。$ cat /etc/shells
で使用可能シェル確認。
#!/bin/sh // /bin/shで実行される。 最も一般的 #!/usr/bin/bash // /usr/bin/bash で実行される
bash特有
- (( )) 式展開
- [[ ]] 比較
- 配列変数
- <(コマンド) ーーーコマンド結果をリダイレクト
変数
変数は数字も全て文字列で格納されています。 なので宣言なしで使えます。 数字の演算の場合は、演算子やコマンドに基づいて一時的に数字に解釈されて演算し結果を文字列で格納するようです。
変数名
英数字とアンダースコアのみで英字かアンダースコアから始めなければなりません。
参照
echoで内容を参照したり、式やif文で使う場合には$や{}をつける。
- $変数名
- ${変数名:開始位置:長さ}」:変数値の開始位置から長さ分の値にする
- ${変数名#文字列}:変数値を前から文字列までを削除した値にする(最短一致)
- ${変数名##文字列}: (最長一致)
- ${変数名%文字列}:変数値を後ろから文字列までを削除した値にする(最短一致)
- ${変数名%%文字列}: (最長一致)
- ${変数名:-文字列}:変数が空の場合、文字列を返す
- ${変数名:=文字列}:変数がからの場合、文字列を返し、変数に文字列をセットする
- ${#変数名}:変数の文字列や配列の長さを返す
- $#:引数の数を表す。
- $@:全ての引数を表す。 for文などで使用する。
$ echo $LANG // en_US.UTF-8 $ echo ${LANG} // en_US.UTF-8 上と同じ $ echo ${LANG:3:2} // US $ echo ${LANG#en} // _US.UTF-8 $ echo ${LANG#*.} // UTF-8 正規表現で.までを削除 $ echo ${LANG%.*} // en_US 正規表現で.以降を削除
代入
- 変数に値を代入したり変更する場合は$をつけずに、更にスペースなしの『=』で代入する。
$ var1 = 123 // 【誤】var1: command not found スペースがあるとエラーになる。 $ var1=123 // 【正】
export
- 環境変数(=グローバル変数)として代入するにはexportを使う。
- exportがなければ、そのスクリプト内でしかその変数を参照できないローカル変数となる。
- シェルで実行した場合はシェルウィンドウ内で有効。
$ myVar1=123 // ローカル変数に値を入れる $ echo $myVar1 // 123 $ printenv myVar1 // 何も表示されない *printenv の場合、変数名に$を付けない $ export myVar1 // myVar1を環境変数にする $ printenv myVar1 // 何も表示されない $ export myVar2=456 // 値を入れて環境変数にする $ printenv // printenvの後に何もないと、環境変数一覧が表示される $ export -n myVar1 // 環境変数取消、myVar1はローカル変数になる
配列
他のプログラムと一緒で[ ]を使います。 forループでの一括取得は、for参照ください。
$ arr=(ichiri Max 123 "Good day") //半角スペースで区切る。 ""でくくると1要素として扱います。 $ arr+=(John) //arr[4]に要素追加。 ()必須。 arr[10]があった場合は、arr[11]を追加する。 $ arr[2]=567 // 123を上書き $ echo ${arr[1]} // Max $ echo ${arr[2]} // 567 $ echo ${arr[@]} // ichiri Max 567 Good day John $ echo ${arr[*]} // ichiri Max 567 Good day John $ echo ${#arr[@]} // 5 要素数取得 $ echo ${#arr[*]} // 5 要素数取得 $ unset arr[1] // 要素削除 $ arr=($変数名) // 変数を配列に変換 $ arr+=($変数名) // 変数を配列に追加 $ myArr2=(${myArr1[@]}) // 配列コピー 参照コピーでなく、もう一つインスタンスを作るディープコピー $ arr[i] // 添字の変数は$はあってもなくても良い $ arr[$i]
readonly
- constと同じ。
- 変更されたくない値はreadonlyをつける。
readonly MY_VAR=1234 MY_VAR=5678 // line 2: MY_VAR: readonly variable
local
- 関数内でlocalを付けると、その関数内でしか使えない。
- 関数内でlocalを付けないと、グローバル変数となってしまうので、大きなスクリプト等で他のグローバル変数を書き換えてしまわないように気を付けること。
関数名(){ local my_var1=ichiri // これはローカル変数 my_var2=123 //これはグローバル変数 local cur prev words cword // 複数のローカル変数を設定 }
演算子
- +, -, *,**(環境によっては\*とエスケープが必要), /, %(余り)
- +=, -=, *=, **=, /=, %=, %%=, <<=, >>=, &=, ^=, |= (これらはletが必要)
- ++変数名、–変数名、(*変数名++、変数名–は評価した後、++,–を実行するので注意)
- & : ビット論理和(AND)
- | : ビット 論理席(OR)
- ^ : ビット排他的論理和(XOR)
- ~ : ビット否定(NOT)各ビットの1と0を全て入れ替え
- << : ビット左シフト。 オーバーフローするとロールする。
- >> : ビット右シフト。 0以下は0でロールしない。
- $(( ))内で計算できる。
- exprは式を評価するコマンド。 正規表現、演算、比較、文字列で使用される。
$ echo $(( 5 * 5)) // 25 $ echo `expr 10*1.23` // 12.3 バッククオートが必要 $ echo `10*1.23` // 10*1.23 exprが無いと文字列とみなされる $ echo `expr length "ichiri"` //6 $ a=5 $ b=10 $ echo $((a*b)) // 50 $(())内の式中は変数に$をつけない $ c=$(((a+10)*b)) // $(())内で更に()を使って計算できる。 結果をcに代入。 $ echo $c // 150
条件
- ==, <=, <, >=, >, !=
- -eq, -ge, -gt, -le, -lt, -ne
- !、&&、 ||
- 三項式 条件式?TRUE時式:FALSE時式
- ( )内は優先して評価する
文字列比較
- 文字列:文字列長が1以上でTrue
- -n 文字列:上と同じ
- !文字列:文字列長が0でTrue
- -z 文字列:文字列長が0でTrue
- 文字列1=文字列2:等しければTrue
- 文字列1!=文字列2:等しくなければTrue
- 文字列が含まれているかどうかを確認する場合
- [[ $PATH == *”java-11-amazon-corretto”* ]]
- この場合==と*の間に半角スペースが必要
- 正規表現の場合は、[[ $string =~ “Hello” ]] とする
- [[ $PATH == *”java-11-amazon-corretto”* ]]
if [[ $PATH == *"java-11-amazon-corretto"* ]]; then PATH=$PATH else PATH=$PATH:/usr/lib/jvm/java-11-amazon-corretto fi
ファイル確認
-e FILE
: ファイルFILE
が存在する場合にTrue-d FILE
: ファイルFILE
がディレクトリの場合にTrue-f FILE
: ファイルFILE
が通常のファイルの場合にTrue-s FILE
: ファイルFILE
のサイズが0(空)でない場合にTrue-L FILE
: ファイルFILE
がシンボリックリンクの場合にTrue-h FILE
: ファイルFILE
がシンボリックリンクの場合にTrue(-Lと同じ)FILE1 -ef EFILE2
: ファイルFILE1
がFILE2
のハードリンクの場合にTrue-r FILE
: ファイルFILE
が読出し権限がユーザーにある場合にTrue-w FILE
: ファイルFILE
が書込み権限がユーザーにある場合にTrue-x FILE
: ファイルFILE
が実行権限がユーザーにある場合にTrue-O FILE
: ファイルFILE
の実体の所有者が実効ユーザーIDと同じ場合にTrue-G FILE
: ファイルFILE
の実体の所属グループが実効グループIDと同じ場合にTrue-u FILE
: ファイルFILE
のsetuidビットが1の場合にTrue-g FILE
: ファイルFILE
のsetgidビットが1の場合にTrue-k FILE
: ファイルFILE
のstickyビットが1の場合にTrue
その他
- -v 変数名:変数が存在する場合にTrue
- !条件式:条件式がfalseの場合にTrue
- 条件式1 -a 条件式2:(and)条件式1と2の両方がTrueの場合にTrue
- 条件式1 -o 条件式2:(or)条件式1と2のどちらかがTrueの場合にTrue
- 文字列1!=文字列2:等しくなければTrue
文字列
連結
$ str1="Good" // $ str1=Good // $ str2=123 // ""がなくても文字列 $ echo "$str1$str2" // Good123 以下全て同じ結果 $ echo "${str1}${str2}" // Good123 $ echo "${str1}""${str2}" // Good123 $ echo $str1$str2 // Good123 $ echo ${str1}${str2} // Good123
ダブルクオート、シングルクオート、バッククオート
- ダブルクオート『” “』
- 内部に変数を含める時に使うのがいい。
- シングルクオート『’ ‘』
- 最も強力で高速
- シングルクオート以外全ての特殊文字を自動的エスケープします
- 内部に変数を入れない時に使うのがいい。
- バッククオート『` `』
- バッククオート内の文字列はコマンドとして実行されます。
- 文字列用ではありません。
$ a=123 $ echo "これは$a" // これは123 $ echo 'これは$a' // これは$a $ echo `これは$a` // これは123: command not found
特殊文字
; & ( ) | ^ < > ? * [ ] $ ‘ ” ` { } 改行 タブ スペース バックスラッシュ
エスケープ『\』しないと特殊な意味があるとして処理される。
文字列操作
変数に文字列を入れて操作します。 sedやawkより簡単。
- ${変数名:開始位置:長さ}」:変数値の開始位置から長さ分の値にする
- ${変数名#文字列}:変数値を前から文字列までを削除した値にする(最短一致)
- ${変数名##文字列}: (最長一致)
- ${変数名%文字列}:変数値を後ろから文字列までを削除した値にする(最短一致)
- ${変数名%%文字列}: (最長一致)
- ${変数名:-文字列}:変数が空の場合、文字列を返す
- ${変数名:=文字列}:変数がからの場合、文字列を返し、変数に文字列をセットする
- ${#変数名}:変数の文字列や配列の長さを返す
- substr($1,2,3):$1に入る変数の文字列の2文字目から3文字切り出す。
- [-n “文字列”]:文字列があればTrue
- [-z “文字列”]:文字列がなければTrue
- [“文字列1” -eq “文字列2”]:同じならTrue
数字
$ a=7 $ printf '%x\n' $((a<<60)) // 7000000000000000 16進表示 $ a=123 $ printf '%x\n' $a // 7b 16進表示 $ printf '%X\n' $a // 7D 16進表示 $ echo $((0xF)) // 15 先頭に0xをつけると16進になる $ echo $((010)) // 8 先頭に0をつけると8進となる $ echo $((2#11)) // 3 先頭に2#をつけると2進となる $ a=0x10 $ echo $a // 0x10 $は0x10という文字としてみなされる $ printf "%d\n" $a // 16 %dで10進表示。 $aは%dでは数字として変換される $ echo $((a*10)) // 160
if
ifとfiでかこむ。
if [ $var1 -eq $var2 ]; then echo “var1=var2” elif [$var3 != 'ichiri'] ;then echo "It's not ichiri" else echo ”It's Ichiri” fi
条件連結
- ANDの場合、[ ]内で『-a』で繋ぐか、[ ]を&&で繋ぐ。
- ORの場合、[ ]内で『-o』で繋ぐか、[ ]を||で繋ぐ。
if [ $str1 -eq "John" -a $str2= "Max" ]; then fi if [ $str1 -eq "John"] && [$str2= "Max" ]; then fi
簡易的記述
以下の例は、引数$1がない場合は、echoが実行され、スクリプトを終了し1を返す。
[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1
Error: should be called from udhcpc ./test.sh: line 3: exit: 1: numeric argument required
以下の例は、subnetという変数があったら、NETMASKにnetmaskを設定するコマンドを文字列として設定する。
[ -n "$subnet" ] && NETMASK="netmask $subnet"
case
case 値 in パターン1 ) 処理1 ;; パターン2|パターン3 ) 処理2 ;; //パターン2か3の時実行 パターン4 ) 処理3 break;; // breakでforやwhileから抜け出せる … パターンn ) 処理n ;; esac // case の逆で終わりなのかな?
for(ループ)
- forもwhileも、breakとcontinueが使える。
inの方法
for 変数 in 値リスト do 処理 done
func1(){ for a in "$@" do echo $a done } func1 12 56 dog
12 56 dog
i++の方法
myArray[0]=John myArray[1]=Max myArray[2]=Todd myArray[3]=Erwin myArray[10]=ichiri for ((i=0; i<${#myArray[*]}; i++)) do echo $i = ${myArray[i]} done
0 = John 1 = Max 2 = Todd 3 = Erwin 4 = // 番号が抜けるとこうなるので注意
while
条件がTrueの間は処理を繰り返し実行する。
while [ 条件式 ] do 処理 done
引数引き渡し
- $1, $2, $3 ….はスクリプトファイルに渡される変数。
- $0はスクリプトファイル名
- 関数にも引き渡せる。
- 子スクリプトにも引き渡せる。
echo $1 echo $2 echo $3 echo $0
./test.sh 100 $a $c 100 3 25 ./test.sh
関数
- 関数名(){ }で記述する。 functionを先頭に付けてもいいはずですが、エラーになった事もあるので、function無しがお薦め。
- retrunの値は0~255だけ
- 呼び出す時は、関数名で()は付けない
関数名(){ // functionを省略できる 処理 return 値 } 関数名 引数リスト //関数名に引数を付けて実行 function 関数名(){ 処理 return 値 }
引数は$1,$2,$3等で受取るか、
myVar1=123 func1(){ echo $1 $2 echo $3 echo $myVar1 let myVar2=567 } func1 12 56 dog echo $myVar2
引数がいくつあるか分からない時は”$@”で受取、forでループして使うのがいい。
myVar1=123 func1(){ for a in "$@" do echo $a done echo $myVar1 let myVar2=567 } func1 12 56 dog echo $myVar2
コマンド
- sleep 10:10秒待つ
- exit:シェルスクリプトを終了する。 exit 0 等とします。
- read str :キーボードからの入力を受け取りstrに入れる
- run-parts -a 引数 ディレクトリ:run-partsで指定したディレクトリ内の実行ファイルを実行すっる。
外部スクリプト
- sourceでパスとファイル名を指定して内部に書かれた関数を使用できる。
- インクルードのような感じ。
- 子スクリプトの実行ではない。
source ./myfunctionfile #或いは、 . ./myfunctionfile
シェルスクリプトとは
- Linuxのコマンドプロンプトで使う複数コマンドを順番に実行するプログラム
- Linuxのコマンドプロンプトはシェルが対応している
- シェルは殻で核となるKernelを覆っていて、シェルを通じてKernelとやり取りする
- シェルで実行するスクリプト(プログラム)でシェルスクリプト
- コマンドプロンプトで実行するのもインタープリターなので、シェルスクリプトもインタープリター
実行
シェルスクリプトを実行するには
- 実行形式にする必要がある
- 『source』か『./』か『sh』を先頭につけて実行する
$ sudo chmod +x test.sh // 実行形式にしています。 $ ./test.sh $ source test.sh // ./で実行するのと同じ $ sh test.sh // ./で実行するのと同じ $ sh -v test.sh // -v オプションで、コマンドごとに表示して実行する。デバッグ用。 -xも同じだが、コマンド先頭に+がつくので見やすい。 //シェルスクリプト内で、set -vからset -まで有効
その他
cd
シェルスクリプト内で既存ディレクトリからcdで別のディレクトリに移動すると現在シェルスクリプトの続きを実行出来なくなる。 その為、( )内でシェルコマンドを実行して実行したいファイルがあるディレクトリを変数に入れて、絶対パス指定で実行して、シェルスクリプトの最後の行でcdして移動するのが良い。
GG_ROOT=$(cd $PWD/../../../..; pwd)
コンソールの色付け
コンソールの色が白の場合、いつも~/.bashrcに以下を入れると色付けできて便利。 組込Linuxでデバッグする際、メッセージが何十行にもなるとコンソールの色も白だと区切りが分かりづらい。
export PS1='\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
echoで改行
echoコマンドでは『-e』オプションをつけると『\n』で改行する
$ echo "これは\n100円" これは\n100円 $ echo -e "これは\n100円" これは 100円
echoオプション
-e
:エスケープシーケンスを解釈します。-n
:改行を出力せずにテキストを表示します。-E
:-e
オプションを無効にします(デフォルト)。
出力
- 1>&2 標準出力もエラーとして出力
- 1>2 エラーだけ出力
- 2>&1 エラーも標準出力として出力
- 2>1 標準出力だけ出力(*使わない事)
- 2>ichiri.txt エラー出力をichiri.txtに出力
- 1>ichiri.txt 標準出力をichiri.txtに出力
- 2>a.txy 1>b.txt エラーも標準出力もそれぞれ別のファイルに出力
- /dev/null 出力を捨てる
- command > /dev/null 2>&1 はエラーを標準出力にマージして、/dev/nullで捨てるのでエラーも何も出力されないので使わないこと。
- 0は、標準入力
- >の代わりに>>にすると、ファイルの一番下に追記する。
Linux機能起動
シェルで実行するようにスクリプト内に記述する。
/etc/init.d/httpd stop udhcpc -i wlan0
別スクリプトを呼び出す
- myScript.sh からsubnet変数を引数に付けてmyNext.shを呼び出します。
- myNext.shが完了したら,myScript.shを継続します。
subnet=255.255.255.0 ./myNext.sh $subnet
戻値
returnで返せる戻値はfunctionかソースのシェルスクリプトだけ。
実行オプション
実行オプションを作りたい時は、-aや-bの様に短くする。
getoptsで解析する。
タイムスタンプ
TIMESTAMP=$(date "+%Y/%m/%d-%H:%M:%S")
呼び出し元ファイル名
psで取った最後(tail -n 1)の行のフィールドの5番目からNF番目までをprint
CALLER=$(ps $PPID | tail -n 1 | awk '{c="";for(i=5;i<=NF;i++) c=c $i" "; print c}')
- $PPID:呼び出し元Parent Process ID
- NF :フィールド数
- awkは文字列の複雑な処理。 フィールドごとに処理できる。 awk専用言語。
- sedは行単位で置き換えや削除などの単純な文字列の処理。
正規表現
『Javascript, Python, PHP, Go 正規表現の記法と一覧』
シェルスクリプトの正規表現もJavascriptやPythonと大体同じですが、まだまとめられていません。 そのうち必要になったら上記ページにまとめていきます。
コメント