bash周りのメモ

August 03, 2021

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
done
while リダイレクトを使う
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
冒頭に入れて相対パスを使えるように

sh スクリプト内で安全に相対パスを使う

#!/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

オレの TIPS 備忘録: 配列・連想配列

やりたいこと 記述 備考
配列を作る 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 で連想配列を実装

bash で連想配列 ≪ Linux 修験道

ブレース展開

アスタリスクを付けると、シェルが先にアスタリスクを展開するため存在するファイルをみつけてくれる。

$ 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
done

curl で 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 ]; then

bash での確認問い合わせ

参考 参考

#!/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
done

select による menu 使用例

select var in x{a,b,c,d}y;
do
  echo your input is $REPLY
  echo $var was selected
done

文字列の数値チェック

B シェル FAQ - torowiki

例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
# => st

while 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) # スペースあけるとうまくいく
  3

read という内部コマンドを使うとうまく動作します。 read は標準入力から読み込んだデータを引数に指定した変数に代入します。 “find”の出力結果をパイプで流して、“read”を使って順番に変数に代入すると、“find”の実行結果が長くても動作しますし、複数のコマンドの実行も簡単に行えます。

find 処理が入る | while read f; do
echo $f
echo $(basename $f)
done

0 バイト以上なら実行

[ -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 を使うなら理解しておきたいアルゴリズム - 抽出・ソート・結合・集計 アルゴリズムが参考になる。

Stray Penguin BASH

Linux で相対パスを絶対パスに変換する。

bash の配列変数に関する Tips 配列を一気に処理する処理がサンプル込みでまとまっていてわかりやすい。

bash スクリプトを書く時に参考にさせていただいたりするサイト

Super Technique 講座~ bash 超プログラム術

bash,awk,sed - スキルアップ輪講

Linux-Unix Cheat Sheets - The Ultimate Collection


Profile picture

Written by tin-machine 技術関連のメモ Twitter