読者です 読者をやめる 読者になる 読者になる

カメニッキ

カメとインコと釣りの人です

最近覚えたシェルスクリプトの小ネタ

LinuxCommand

シェルスクリプトから教えていただいた。忘れないように書いとく

(追記)聞いて、自分の記憶した内容をそのまま書いちゃったので、ちゃんとマニュアル通りか確認してなかったので反省

$ man bash

目次

  • xargsでfunctionを叩く
  • 連想配列もどき
  • 変数間接参照
  • なんでもかんでもawkで整形しない
  • 文字列の末尾から数えて○文字目を△文字取り出す
  • 番外編:やたら if [ ]; then を使わない

Markdownで目次の書き方がわからんかった

xargsでfunctionを叩く


下の例だと find xxxx | xargs cp xxxx ってかけば良さそうでイマイチだけど、もっと複雑な処理やらせたいときに。

  • 前はこう書いてた
  #!/bin/bash

  for x in `find /var/www -name xxx`
  do
    cmd="cp -v /hoge/fuga/xxx $x"
    [[ `md5sum $x | awk '{print $1}'` != "xxxxxxxxxxxxxxxxxxxxxxx" ]] && eval $cmd | :
  done
  • xargsで5多重で同じことやらせる
#!/bin/bash

function sample() {
  cmd="cp -v /hoge/fuga/xxx $1"
  [[ `md5sum $1 | awk '{print $1}'` != "xxxxxxxxxxxxxxxxxxxxxxx" ]] && eval $cmd | :

}

# これしたらxargsから呼べることをしらなかった
export -f sample

find /var/www -name xxx | xargs -P5 -I{} bash -c "sample {}"

連想配列もどき


# 連想配列もどきのHを定義
declare -A H
H["a"]="682f7xxxxxxxxxxxxxxxxxxx0"
H["b"]="7808axxxxxxxxxxxxxxxxxxx2"
H["c"]="c6ba1xxxxxxxxxxxxxxxxxxx6"
H["d"]="64186xxxxxxxxxxxxxxxxxxxb"
H["e"]="50d1dxxxxxxxxxxxxxxxxxxxc"

for x in a b c d e
do
# とりだす
echo ${H["$x"]}
done

ちなみに、

  • こっちは配列見える
declare -A H
H["a"]="682f7xxxxxxxxxxxxxxxxxxx0"
H["b"]="7808axxxxxxxxxxxxxxxxxxx2"
H["c"]="c6ba1xxxxxxxxxxxxxxxxxxx6"
H["d"]="64186xxxxxxxxxxxxxxxxxxxb"
H["e"]="50d1dxxxxxxxxxxxxxxxxxxxc"

function sample(){
  # 682f7xxxxxxxxxxxxxxxxxxx0 
  echo ${H["a"]}
}

sample
  • こっちは配列見えない
#!/bin/bash

declare -A H
H["a"]="682f7xxxxxxxxxxxxxxxxxxx0"
H["b"]="7808axxxxxxxxxxxxxxxxxxx2"
H["c"]="c6ba1xxxxxxxxxxxxxxxxxxx6"
H["d"]="64186xxxxxxxxxxxxxxxxxxxb"
H["e"]="50d1dxxxxxxxxxxxxxxxxxxxc"

function sample(){
  # 682f7xxxxxxxxxxxxxxxxxxx0
  echo ${H["a"]}
  echo "hoge"
}
# ここで配列はexportされないらしいけど、あんまり詳しく調べてない。当たり前の動き?
export -f sample
find ~/test -type f  | xargs bash -c "sample"

変数間接参照


#!/bin/bash

var1="xxxxxxxxxxxxxxxx"
var2="yyyyyyyyyyyyyyyy"
var3="zzzzzzzzzzzzzzzz"

for i in `seq 1 3`
do  
  # 変数を使って作る変数名は一度変数に入れなきゃだめ
  tmp="var${i}"
  echo ${!tmp}
done

なんでもかんでもawkで整形しない


/home/hoge/fuga/piyo/php5.2.cgi のようなパスから、末尾の php5.2.cgi のみ抽出し、かつその値を利用して色々処理したい時に、つい以下のようなことを書いてた。

[root@hoge ~]# cat test.sh
#!/bin/bash

for x in `find ~/home/users/php-bin/ -path "*/.php-bin/php*.cgi"`
do
echo $x | awk -F/ '{print $10}'
done

このくらいのことをawk出してるとコストが高いので、bashの変数操作を使って以下の様に書く

[root@hoge ~]# cat test2.sh
#!/bin/bash

for x in `find ~/home/users/php-bin/ -path "*/.php-bin/php*.cgi"`
do
echo ${x##/*/}
done

すると

[root@hoge ~]# time ./test.sh
・・・
real    0m8.316s
user    0m2.367s
sys 0m7.828s

[root@hoge ~]# time ./test2.sh
・・・
real    0m0.763s
user    0m0.357s
sys 0m0.394s

圧倒的に早い!!!

捕捉 この例だと

find ~/home/users/php-bin/ -path "*/.php-bin/php*.cgi" | awk -F/ '{print $10}'

でいいけど、今回のこれは、①findしたパス②awkで整形した値両方を使用して、いろいろ処理する必要があって、forを利用してた

文字列の末尾から数えて○文字目を△文字取り出す

以下は乱暴な例ですが、 5.xx の値に応じて処理したい要件があった。 先頭の文字はファイルによってバラバラの長さのため、cutコマンドでは取得できないため、「末尾から何文字目」という指定をしたかった

[user ~/work/test]$ ll
total 0
-rw-r--r--  1 user  staff     0B  6 28 00:49 hogehogeho_ver5.2.sh
-rw-r--r--  1 user  staff     0B  6 28 00:49 xxxx.yyyy-abcdefghijk_ver5.2.sh
-rw-r--r--  1 user  staff     0B  6 28 00:49 xxxxxxxxxxxxxxxxxxxxxxxxxxx_ver5.2.sh
-rw-r--r--  1 user  staff     0B  6 28 00:49 yyyyyyyyyyyy-aaaaaaaaaaaaaaaa_ver5.3.sh
[user ~/work/test]$ for x in `find . -type f`
> do
> echo ${x:${#x}-4:1}
> done
2
2
2
3

番外編:やたら if [ ]; then を使わない


testコマンドを使うとフォークしてしまって大量に叩くとパフォーマンス悪いとのこと。

[PMAC226S ~/work]$ cat test.sh
#!/bin/bash

for i in `seq 1 1000000`
do
  if [ "$a" = "hoge" ]; then
    echo "hoge"
  else
    echo "fuga"
  fi
done

[PMAC226S ~/work]$ cat test2.sh
#!/bin/bash

for i in `seq 1 1000000`
do
  [[ "$a" = "hoge" ]]  && echo "hoge" || echo "fuga"
done

があった時、後者の方が結構はやい 

[PMAC226S ~/work]$ time ./test.sh
・・・
real    0m30.164s
user    0m20.777s
sys 0m2.917s

[PMAC226S ~/work]$ time ./test2.sh
・・・
real    0m25.246s
user    0m17.777s
sys 0m1.812s