bash 5.1.4 で 2021/09/09 確認
文字列操作
VAR=onetwothree # 以下はこの VAR を操作するとして
| 表現 | 結果 | 意味 |
|---|---|---|
| ${VAR/two/four} | onefourthree | 値の中の文字列 two を four に置換 |
| $VAR | onetwothree | (元の変数自体は変わらない) |
| ${#VAR} | 11 | 文字数 |
| ${VAR/two} | onethree | 置換文字列を指定しないと一致文字列が削除される |
| ${VAR#one*t} | wothree | 値の前方からマッチする最短の文字列を取り除く |
| ${VAR##one*t} | hree | 値の前方からマッチする最長の文字列を取り除く |
| ${VAR%t*ee} | onetwo | 値の後方からマッチする最短の文字列を取り除く |
| ${VAR%%t*ee} | one | 値の後方からマッチする最長の文字列を取り除く |
| ${VAR: 0: 3} | one | 一番左の文字から 3 文字を取り出す |
| ${VAR: 2: 3} | etw | 2 番目の文字から 3 文字分を取り出す |
| ${VAR: 2} | etwothree | 第 2 パラメータを省略すると`末尾まで’の意となる |
| ${VAR: -4: 3} | hre | 末尾から数えて 4 番目の文字から、3 文字分を取り出す |
| ${VAR: -4} | hree | 第 2 パラメータを省略すると`末尾まで’の意 |
| ${VAR: -1} | e | 最後の 1 文字を取り出すにはこう |
シェルの組み込みコマンド
| コマンド | 効果 |
|---|---|
| : | 何もしないで終了コード 0 を返す。while : ;do …;done のように無限ループさせるのによく使う。 |
| . filename | filename で指定されたファイルを読み込んで中に書かれたコマンドを現在の環境で実行する。include のイメージ。 |
| break [n] | ループのを抜ける。n を指定すると n レベル外のループを抜ける。 |
| continue [n] | ループの次の繰り返し分から実行を継続する。n を指定すると n レベル外のループで実行を継続します。 |
| read [name …] | 標準入力から 1 行読み込み、最初の単語を最初の name に代入し、2 番目の単語を 2 番目の name に代入します。name が指定されなかったら REPLY に代入される。 |
| exec command [arguments] | 現在のシェルはこの command に置き換えられます。新しいプロセスは生成されません。exec 以降のスクリプトが実行されることはありません。 |
| exit [n] | ステータス n でシェルを終了させる。 |
| return [n] | ステータス n で関数を終了する。 |
| export name[=word] … | name を環境変数に設定します。word が指定された場合は環境変数 name に word が設定されます。 |
| よく使う構文 | 結果 | 効果 |
|---|---|---|
| $((1+1)) | 2 | 足し算 |
| $((2-2)) | 0 | 引き算 |
| $((2*2)) | 4 | 掛け算 |
| $((2/2)) | 1 | 割り算 |
| $((7%2)) | 1 | 余り |
| ++num あるいは num++ | インクリメント | |
| —num あるいは num— | デクリメント |
文字列分割して配列に入れる
OLDIFS=IFS
IFS=','
declare str='aaa,bbb,ccc'
declare -a record=($str)
IFS=OLDIFS
文字列の検索
declare str1='abcd'
declare str2='cd'
OLDIFS=IFS
IFS="$str2"
declare temp=($str1)
declare -i str1_len=${#str1}
declare -i temp_len=${#temp}
IFS=OLDIFS
if [ $str1_len -eq $temp_len ]; then
declare -i result=-1 #見つからなかったら-1
else
declare -i result=$temp_len # 見つかった場合はその位置
fi
read
データファイルを処理するためのちょっとしたスクリプトを書くのに ruby を使っていたけど,bash でもいろいろできるのね. # (1) パイプを使う
cat $file | while read line; do
echo $line
donewhile リダイレクトを使う
while read line; do
echo $line
done < $fileスクリプトを実行したパス
| コマンド | 効果 |
|---|---|
| echo $(cd $(dirname $0);pwd) | 実行したディレクトリ (ファイル名無し) |
| echo $(cd $(dirname $0) && pwd)/$(basename $0) | 自身のスクリプトの絶対パス(ファイル名有り) |
| bash -x hoge.bash | デバック |
| PATH=${PATH}:/usr/sbin:/sbin | コマンドサーチパスに登録 |
| [ ! -d /var/tmp/backup ] && mkdir -p /var/tmp/backup | バックアップ用ディレクトリが無ければ作成 |
特定のパスを文字列処理
path = ../../../hoge
echo $(cd $(dirname $path);pwd)
echo basename $path
冒頭に入れて相対パスを使えるように
#!/bin/bash
SCRIPT_DIR=`dirname $0`
cd $SCRIPT_DIR絶対パスの取得
#!/bin/sh
echo $(cd $(dirname $0);pwd)シンボリックリンクのパス
readlink コマンドがある。
配列
| 表現 | 結果 | 意味 |
|---|---|---|
| declare -a array=(one two three four five six) | 定義 | |
| ${array[0]} | one | 値を参照 |
| ${#array[@]} | 6 | 配列の個数 |
| hogege=$(IFS=”,” ; echo ”${array[*]}“) | one,two,three,four,five,six | 配列を IFS で連結 |
| array=(5 ”${array[@]}“) | 5 one two three four five six | 最初に追加 |
| array=(”${array[@]}” 6) | one two three four five six 6 | 最後に追加 |
| array[3]=99 | one two three 99 five six | 4 番目を 99 に変える |
| ${array[@]: 0: 1} | one two | 0 番目から 2 個とりだす |
| ${array[@]: 2: 2} | three four | 3 番目から 2 個とりだす |
| ${array[@]: 2} | three four five six | 第 2 パラメータ省略で最後 → まで |
| ${array[@]: -4: 3} | two three four | 後ろから 4 番目の要素から 3 つを取り出す |
| ${array[((${#array[@]}-1))]} | six | 要素数の分からない配列から最後の要素を取り出す方法 |
| ${array[@]/f/えふ} | one two three えふ our えふ ive six | 置換 |
| ${array[@]#*o} | ne three ur five six | 最短前方一致削除 → o まで消えてる |
| ${array[@]##*e} | two four six | 最長前方一致削除 → one が最後まで消えてる |
| ${array[@]%e*} | on two thre four fiv six | 最短後方一致削除 ← one n の最後の e だけ消えてる |
| ${array[@]%%e*} | two four six | 最長後方一致削除 ← |
配列の全てに同じ処理をする
for (( I = 0; I < ${#array[@]}; ++I ))
do
// ${array[$I]}を使った処理
done
for num in "${array[@]}" ; do echo $num ; done
for each 文
for field in "${fields[@]}";do
done
配列に毎行ごと while read line で読み込み
declare file='test'
declare line
while read line ; do
array=("${array[@]}" "$line") #$line をダブルコーテーションでくくるの重要。くくらないと『SCO Unix』みたいな空白を含む値が正しく配列に入らない(分割されてしまう)
done < $file
echo ${array[@]}
添え字でアクセス
declare -a array=("a" "b" "c")
for (( i = 0; i < ${#配列名[@]}; i++ ))
do
echo ${array[$i]}
done
配列から取り出す( 添え字なし )
for fig in "${array[@]}" ; do
echo $fig
done
bash_連想配列
RHEL6 以降だと使える シェルスクリプト/文法/配列(連想配列) - yanor.net/wiki
| やりたいこと | 記述 | 備考 |
|---|---|---|
| 配列を作る | declare -A 配列名 | 明示的に宣言しないといけないらしい。 |
| 配列を参照する | ${配列名[キー]} | |
| 配列の要素を変更する | 配列名[キー]=値 | |
| 配列に要素を追加(push) | ARR1=(${ARR1[@]} 0) とすると 0 が追加される | |
| 配列と配列をマージ | ARR1=(${ARR1[@]} 0) |
サンプルコード
単語を数える例のやつ - harry’s memorandum
bash の version 4 からは連想配列が使える。知らない間に機能アップしている。 array=(foo bar hoge foo foo bar fuga hoge) declare -A h # 連想配列 h を宣言
for e in ${array[@]}; do
h[$e]=$(( h[$e] + 1 ))
done${h[*]}はvalues, ${!h[*]}はkeys
for e in ${!h[*]}; do
echo "$e => ${h[$e]}"
done古い bash で eval で連想配列を実装
ブレース展開
アスタリスクを付けると、シェルが先にアスタリスクを展開するため存在するファイルをみつけてくれる。
$ echo /{s,}bin/{a,gr*}
/sbin/a /sbin/grub /sbin/grub-install /sbin/grub-md5-crypt /sbin/grub-terminfo
/sbin/grubby /bin/a /bin/grep引数を 2 つ使うコマンドにはサフィックスを付けてブレース展開すると非常に便利になります。
$ echo testing.rb{,.backup}
testing.rb testing.rb.backupブレース展開を利用して日付毎にバックアップ
$ cp -p testing.rb{,.`date +%Y%m%d`}
$ ls testing.rb{,.`date +%Y%m%d`}
testing.rb testing.rb.20090211ブレース展開を利用して一括でファイルをリネーム
$ ls
audit2allow autoconf automake automake-1.6 autoreconf
authconfig autoheader automake-1.4 automake-1.7 autoscan
authconfig-tui autom4te automake-1.5 automake-1.9 autoupdate$ for i in \*; do mv $i{,.bak}; done$ ls
audit2allow.bak autoconf.bak automake-1.4.bak automake-1.7.bak autoreconf.bak
authconfig-tui.bak autoheader.bak automake-1.5.bak automake-1.9.bak autoscan.bak
authconfig.bak autom4te.bak automake-1.6.bak automake.bak autoupdate.bak関数 定義
logging() {
echo -n $1 | logger -t Backup -i
}
関数で複数の引数
function show_sum() {
declare -i num1=$1
declare -i num2=$2
declare -i total=num1+num2
echo $total
return 0
}
show_sum 3 4 の様にして使う
時間がかかる処理でクルクルまわるスピナー表示
#!/bin/bash
chars="/-\|"
while :; do
for (( i=0; i<${#chars}; i++ )); do
sleep 1
echo -en "${chars:$i:1}" "\r"
done
donecurl で POST
$ cat text.txt | curl -sS -X POST -d @- http://example.com/boopooエラー処理をシンプルに行う
command ... || { echo ... ; exit 1 ; }- {}でくくるのを忘れないように
- exit 1 の後のセミコロンを忘れないように
bash の test で変数が null だった場合エラーになる
if [ $answer = yes ]; then
これは $answer がから文字列だった場合、 if [ = yes ]; then と評価されエラーになります。ではダブルクォーテーションで囲めばよいのか?
if [ "$answer" = yes ]; then
実はこれでもまだ安全ではありません。もし $answer が -f だった場合 if [ -f = yes ]; then となりエラーと判断されるかもしれません。
最も安全場記述方法は以下のように確実に文字列として評価されるように記述することです。
if [ x"$answer" = xyes ]; thenbash での確認問い合わせ
#!/bin/sh
while /bin/true
do
echo "-------------------------------------------"
echo "よろしいですか?(yes/no)"
echo -n " => "
read ans
echo "-------------------------------------------"
case ${ans} in
[Yy]|[Yy][Ee][Ss])
echo "yesが押されました"
continue ;;
[Nn]|[Nn][Oo])
echo "noが押されました"
continue ;;
*)
echo "キャンセルされました。"
exit 0;;
esac
doneselect による menu 使用例
select var in x{a,b,c,d}y;
do
echo your input is $REPLY
echo $var was selected
done文字列の数値チェック
例1、引数$1 が数値のとき numeric を、数値で無いとき not numeric を表示する。
NUMERIC_CHECK=`echo "${1}" | awk '/[^0-9]/ {print}'`
if [[ ! -z $NUMERIC_CHECK ]] ; then
echo "not numeric"
else
echo "numeric"
fi文字列の部分取得 cut コマンドを使う。
#!/bin/bash
echo 'test' | cut -c 3,4
# => stwhile read で find の出力を得る
ウノウラボ by Zynga Japan: シェル(bash)スクリプトを書くときの Tips
find ... | while read f; do
echo $f
echo $(basename $f)
done
find ... | while read f; doゲストの HDD 一覧をとる RHEL6 以上
LANG=C virsh domblklist ${name} |grep -vE 'Target|-----|^$' | while read drive path ; do
echo "${name}: ${drive}: ${path}"
doneタイムアウト処理
timeout というコマンドがある環境もある。
バックグラウンドで非監視プログラムを実行して,フォアグラウンドでタイムアウト処理を実行する.もっとうまく書けそうだな.
timeout=5
touch run.lock;
($*; rm run.lock) &
cnt=0
while true
do
if [ ! -f run.lock ]; then
break;
fi
if [ $cnt -ge $timeout ]; then
break;
fi
echo "sleep [$cnt]"
sleep 1;
cnt=`expr $cnt + 1`
done
if [ -f run.lock ]; then
pkill -TERM $1
fiスクリプトにも寿命がほしい (第三のペンギン 2006-12-28)に書かれていた方法.
timeout=5
trap "exit 1" INT
(sleep $timeout; ps $$ && kill -INT $$; sleep 1; ps $$ && kill -KILL $$) &> /dev/null &
$*
exit 0ジョブが終了するのを待つ
foo& bar& とかでバックグラウンドジョブを作成した後に、それらが全て終了するのを待つ方法。
その1(フィルタを適当に変えれば柔軟に利用可能)
wait $( jobs -l | perl -pe's/._\s+([0-9]+) Running ._/$1/' )その2
while wait %% 2>/dev/null; do :; done一時ファイルを作らずにコマンド出力をファイルのように扱う、プロセス置換
diff <(ssh host cat file) <(ssh host cat file)y を入力させて実行の最終確認を行う(安全装置)
以下の 1 行をスクリプトの頭に書いておけばよい read -p ‘Are you sure? [y/N] ’ -t10 k && test “x$k” == “xy” || exit 1
/bin/sh とは非互換を含むので注意
気をつけたい。 ウノウラボ Unoh Labs: シェル(bash)スクリプトを書くときの Tips
$(...)
コマンドを"$(", ")"で囲むと実行結果をコマンドラインに代入してくれます。
一見これは"`"(バッククオート)と同じに見えますが、"$()"にはネストができるという利点があります。
例えばシェルスクリプト自身の絶対パスを取得するのは次のようにできます。
echo $(cd $(dirname $0);pwd)
バッククオートだと一時変数に代入しないとできませんが、"$()"を使うとネストが可能なので、一発で取得する事ができます。
ちなみにこれは、相対パスでも絶対パスでもうまく動作します。
((...)), $((...))
元々シェルは数値演算能力を持たないので、シェルスクリプト内で数値演算を行うには"expr"という外部コマンドを呼び出していました。
最近のシェルだと"((", "))"で囲むことで数値演算を行ってくれるようになってます。
"((...))"を評価した結果がそれぞれ、"0", "0以外"で終了ステータスが "1", "0"になります(ややこしや~)。
演算結果をコマンドラインに代入したい場合は"$((", "))"で囲めばできます。
"$((...))", "((...))"を使うメリットはいくつかあります。- 内部コマンドなので処理が速い
- 演算子の間にスペースをあける必要がない
if ((1)); then # ((1))は終了ステータスが"0"なので"true"が表示される
echo true
else
echo false
fi $ echo $((1+2)) # 演算子の間にスペースがいらない
3
$ echo $(expr 1+2) # expr だと演算子の間にスペースが必要
1+2
$ echo $(expr 1 + 2) # スペースあけるとうまくいく
3read という内部コマンドを使うとうまく動作します。 read は標準入力から読み込んだデータを引数に指定した変数に代入します。 “find”の出力結果をパイプで流して、“read”を使って順番に変数に代入すると、“find”の実行結果が長くても動作しますし、複数のコマンドの実行も簡単に行えます。
find 処理が入る | while read f; do
echo $f
echo $(basename $f)
done0 バイト以上なら実行
[ -s /var/tmp/setup_error.txt ] && { echo ${0##/} ; echo ” ; } ※最後の;を忘れると文法エラーになる。
変数が空かどうか
シェルスクリプト(sh)で、ある変数が空文字列かどうかを調べるには、test コマンドの -n (Nonzero length) または -z (Zero length) を使う……と書いてあるんだけど、コツがいる。
if [ -n $HOGE ]; then
echo "nonzero だよ。"
fi
↑ これはダメ
if [ -n "$HOGE" ]; then
echo "nonzero だよ。"
fi
これなら OK。正規表現で文字列判別
bash の正規表現マッチングの使い方 - adsaria mood
[[ ${server} =~ d0* ]] && echo ${server}
みたいに使う。肝心の正規表現部分は、コーテーションしないこと(でないと、文字列として扱われてしまう)bash でリモートコントロール
nc コマンドと bash の/dev/tcp で通信 - suztomo の日記
ポートが開きっぱなしなので、LAN 内に閉じた環境でのみかな、と思う。
シェルスクリプトで変数に改行を含めたい
(Linux)シェルスクリプトで変数に改行を含めたい : 3 流プログラマのメモ書き
素直にソースコード中に改行を入れれば良い
VAR="test1
test2
test3
"
echo "${VAR}" #改行含まれる
test1
test2
test3
echo ${VAR} #これだと改行コードが無視される。
test1test2test3####### 複数文字列から『2 個づつ取り出す』
AWS インスタンスの一覧を mallard:list_instances.bash でメールしてた時、下記のような処理を書いた
instances=$(aws ec2 describe-instances --filters Name=instance-state-name,Values=${status} |egrep "InstanceId|InstanceType" | awk '{print $2}' | sed 's/,//' | paste -d' ' - - |sed 's/\"\ \"/:/g' )ファイルの内容を変数に読み込み
declare file='test'
declare line
while read line ; do
echo $line
done < $file
文法チェック
bash -n script.sh
デバック用に実行中のコマンドを表示
bash -x script.sh
#ファイル入出力
入力
declare line
while read line
do
done < $file
ワンライナー
while read hoge ;do
hoge=${hoge%%]*}; hoge=${hoge#\*\[} ; hoge=${hoge: 0: 2}
echo $hoge; let ++"_${hoge}" ; echo "\_${hoge}" ; echo "${\_01}" ;
done < ${zcat /usr2/logs/201103/hoge/www/www/access-20110301.Z | grep -E '219.122.112.8[0-2]|219.122.112.88' )
参考
シェル: n 個ずつ畳む時に xargs -L よりも paste (速い・低コスト)
1
2
3
4
を
1 2
3 4
みたいに畳む方法が紹介されていた。xargs との比較もある。
Bash を使うなら理解しておきたいアルゴリズム - 抽出・ソート・結合・集計 アルゴリズムが参考になる。
bash の配列変数に関する Tips 配列を一気に処理する処理がサンプル込みでまとまっていてわかりやすい。