Bashメモ
多重代入の実現
Bashから別のプログラムを呼んで複数の計算結果を返して変数に代入したい場合、以下の形式に沿っておくと簡潔に書ける。例えば、,
で挟んで複数の計算結果を返す場合、
eval $(echo $result | sed -r 's/(.*),(.*),(.*)/one=\1;two=\2;three=\3/')
と書ける。 後方参照が二桁になってしまうような場合は、
eval $(echo $result | sed -r 's/([^,]+,){9}(.*)/ten=\2/')
と、量指定子でシフトさせて取り出す。
性能改善
シェルスクリプトの中で1行ずつ変数を分割する際には、cutとかawkとか余計なプロセスを起動せずsetを使って分割した方が効率的 - 双六工場日誌
という記事を見て、余計なプロセスを起動していることを認識。 以下、2つのスクリプトを作成してベンチマーク。(Cygwin環境)
a.sh
#!/bin/bash.exe line="aaa,bbb,ccc,ddd,eee" for i in $(seq 1 100); do eval $(echo $line | sed -r 's/^([a-z]+),([a-z]+),([a-z]+),([a-z]+),([a-z]+)$/a=\1;b=\2;c=\3;d=\4;e=\5/g') done echo $a;echo $b;echo $c;echo $d;echo $e;
b.sh
#!/bin/bash.exe line="aaa,bbb,ccc,ddd,eee" _IFS=$IFS IFS=$',' for i in $(seq 1 100); do set -- $line a=$1;b=$2;c=$3;d=$4;e=$5 done IFS=$_IFS echo $a;echo $b;echo $c;echo $d;echo $e;
$ time bash a.sh aaa bbb ccc ddd eee real 0m3.193s user 0m0.633s sys 0m2.018s $ time bash b.sh aaa bbb ccc ddd eee real 0m0.062s user 0m0.015s sys 0m0.030s
Bash 文字列操作覚え書き
それほど使うことがないものあるが${a:-}
や${a%.*}
、${a//}
あたりは頻出。
$ echo ch{,annel,arset} ch channel charset $ echo ch{1..13} ch1 ch2 ch3 ch4 ch5 ch6 ch7 ch8 ch9 ch10 ch11 ch12 ch13 $ echo ch{36,40,44,48} ch36 ch40 ch44 ch48 $ chs=$(echo ch{36,40,44,48} | sed 's/ /,/g') $ echo $chs ch36,ch40,ch44,ch48 $ echo ${#chs} 19 $ echo ${chs/,/, } ch36, ch40,ch44,ch48 $ echo ${chs//,/, } ch36, ch40, ch44, ch48 $ echo ${chs::4} ch36 $ echo ${chs:5:4} ch40 $ echo ${chs: -4} ch48 $ echo ${chs:5} ch40,ch44,ch48 $ echo ${chs: -9:4} ch44 $ echo ${chs#*,} ch40,ch44,ch48 $ echo ${chs##*,} ch48 $ echo ${chs%,*} ch36,ch40,ch44 $ echo ${chs%%,*} ch36 $ echo $cchs $ echo ${chs:-Nothing} ch36,ch40,ch44,ch48 $ echo ${cchs:-Nothing} Nothing $ echo ${chs:+Nothing} Nothing $ echo ${cchs:+Nothing} $ echo ${cchs:-$chs} ch36,ch40,ch44,ch48 $ echo $cchs $ echo ${cchs:=$chs} ch36,ch40,ch44,ch48 $ echo $cchs ch36,ch40,ch44,ch48 $ echo ${chs/%?/} ch36,ch40,ch44,ch4 $ echo ${chs/#??/} 36,ch40,ch44,ch48 $ echo {dir1,dir2}/log{1..5}{,.txt} dir1/log1 dir1/log1.txt dir1/log2 dir1/log2.txt dir1/log3 dir1/log3.txt dir1/log4 dir1/log4.txt dir1/log5 dir1/log5.txt dir2/log1 dir2/log1.txt dir2/log2 dir2/log2.txt dir2/log3 dir2/log3.txt dir2/log4 dir2/log4.txt dir2/log5 dir2/log5.txt
- 以下はbash 4.0以降
$ bash --version GNU bash, version 4.2.53(1)-release (x86_64-redhat-linux-gnu) Copyright (C) 2011 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software; you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. $ Chs=$(echo Ch{36,40,44,48} | sed 's/ /,/g') $ echo ${Chs} Ch36,Ch40,Ch44,Ch48 $ echo ${Chs,} ch36,Ch40,Ch44,Ch48 $ echo ${Chs,,} ch36,ch40,ch44,ch48 $ echo ${Chs^^} CH36,CH40,CH44,CH48 $ echo ${Chs~~} cH36,cH40,cH44,cH48
改行入りの文字列を扱うとき
コマンドやプログラムの結果に改行が含まれていて、1行ずつ処理したい場合は、デリミタを示す環境変数であるIFSを改行に変えておく。デフォルトはスペース・タブ・改行になっている。行毎処理が終わったら、デフォルト値に戻しておく。
_IFS=$IFS # 単語の区切り、を一時保存。 IFS=$'\n' # 単語の区切りを、一時的に改行のみにする。ダブるクォートではなく、シングルクォートで。 res=(` ... `) # コマンド実行。 for line in ${res[*]}; do echo $line # 一行ずつ処理できる。 done IFS=$_IFS