tech_memo / linux / shell_command


tech_memo/linux

catのヒアドキュメントの中で変数やコマンドを展開しない

  • ヒアドキュメントの開始文字列をダブルクォートかシングルクォートで囲むと変数展開されなくなる
    #!/bin/sh
     
    a="rilakkuma"
    cat << "EOL"
    [animal]
    ${a}
    EOL
  • 結果
    [animal]
    ${a}

パイプ実行時のパイプ前の終了コードを取得

  • コマンド | tee とかする時に便利
  • 下記のように各パイプの終了コードが下記の場合
    # exit 2 | exit 1 | exit 0
  • 普通にecho $?すると
    # echo $?
    0
  • 各パイプの終了コードを取得するには
    # echo  ${PIPESTATUS[@]}
    2 1 0
  • 最初の終了コードを取得するには
    # echo  ${PIPESTATUS[0]}
    2

sourceしたスクリプト内でそのスクリプトのパスをとる方法

  • $BASH_SOURCE変数に、source xxx.shしたときのxxx.shの相対パスが取得できる
    script_dir=$(cd $(dirname $BASH_SOURCE); pwd)
    • ちなみに、zshでは$0で取得できる模様
  • 参考

eval

関数に引数を渡して値を受け取る

  • 通常の変数
    function func()
    {
      local arg1=$1
      eval $arg1='代入するよ' # 変数名='代入するよ' に展開される
    }
    func test
    echo $test
  • 配列を戻す場合
    function func()
    {
      local arg1=$1
      local list=()
      list+=("hoge")
      list+=("fuga")
    
      eval $arg=\(${hoge[@]}\)
    }

シェルのコード整形

timeコマンドのリダイレクト、変数格納

  • サブシェル実行することでリダイレクトや変数格納ができる
    • リダイレクト
      { time pwd; } 2>&1
    • カレントシェル版
      ( time pwd; ) 2>&1
    • 変数格納
      ret=$(( time pwd; ) 2>&1)

シェルでの括弧の意味

  • 参考 : http://qiita.com/yohm/items/3527d517768402efbcb6
  • Parentheses ()
    • サブシェルを実行する。サブシェルなので変数の変更はカレントシェルに反映されない
      $ A=AAA    
      $ ( A=BBB )
      $ echo $A  
      AAA
    • || や && と組み合わせて、複数のコマンドを実行したいときに便利
      $ ls Nothing_path || ( echo ERROR; echo ERROR2 ) 
      ls: cannot access 'Nothing_path': No such file or directory
      ERROR
      ERROR2
  • Braces {}
    • ()とほぼ同じだが、サブシェルではなく、カレントシェルで実行される
    • なので、カレントシェルの変数操作をしたいときは、こっちを使う。
  • bracket []
    • testコマンドの略式
  • double bracket [[ ]]
    • bash, zsh, kshのみで使える。
    • C言語風の &&、 || 条件や、()括弧、正規表現が使えるので、複雑な条件式を書くときは便利
      [[ 1 -lt 2 && 2 -lt 3 ]]
      [[ abc == a* ]]
      [[ a2 =~ a[0-9] ]]

標準出力・エラーを変数に格納

  • 参考 : http://shellscript.sunone.me/variable.html#標準出力-標準エラー出力の両方を変数に設定する
  • 標準出力・エラーの両方を変数に格納
    VAR=`command 2>&1`
  • 標準エラーのみ変数に格納
    VAR=`command 2>&1 1>/dev/null`

改行を含む変数をecho

  • そのままechoすると改行されない。変数をダブルクォートで囲めばOK
    #/bin/bash
    
    a="ABC
    DEFG"
    echo $a
    echo "$a"
  • 結果
    ABC DEFG
    ABC
    DEFG

xargs

ファイル名にスペースが含まれる場合

  • findコマンドでは、-print0、その後のxargsコマンドでは-0オプションを指定
    find . -name \*~ -print0 | xargs -0 rm

xargsに渡す引数の位置を指定

変数内の文字列置換

  • sedなどのコマンドを利用せずに、文字列置換を行う方法
    • ${変数名#パターン} → 前方一致でのマッチ部分削除(最短マッチ)
      # var="/my/path/dir/test.dat"
      # echo ${var#*/}  
      # my/path/dir/test.dat
    • ${変数名##パターン} → 前方一致でのマッチ部分削除(最長マッチ)
      # var="/my/path/dir/test.dat"
      # echo ${var##*/}
      # test.dat
    • ${変数名%パターン} → 後方一致でのマッチ部分削除(最短マッチ)
    • ${変数名%%パターン} → 後方一致でのマッチ部分削除(最長マッチ)
    • ${変数名/置換前文字列/置換後文字列} → 文字列置換(最初にマッチしたもののみ)
      # var="abcdef abcdef xyz"
      # echo ${var/abc/XXX}
      # XXXdef abcdef xyz
    • ${変数名//置換前文字列/置換後文字列} → 文字列置換(マッチしたものすべて)
      # var="abcdef abcdef xyz"
      # echo ${var//abc/XXX}
      # XXXdef XXXdef xyz
  • 参考

変数内置換を利用して配列作成

  • ${変数名//置換前文字列/置換後文字列} → 文字列置換(マッチしたものすべて)
    # var="1aaaa,2bbbb,3cccc"
    # arry=(${var//,/ })
    # echo ${arry[1]}
    # 2bbbb
    • bashでないと動作しない(はず)ので、zshだと期待する結果にならない。注意。

シェルスクリプト内での~(チルダ)の展開方法

  • シェル上では、下記のようにチルダを利用できる
    % ls ~shishimaru
    a.xml memo.txt
  • シェルスクリプト内では、上記のように展開してくれないが、下記のように記述すると利用できる
    ls `bash -c "echo ~shishimaru"`

対話形式のシェルやコマンドの自動化(expectコマンド)

  • 以下は、cmdを実行して、"*password:*"と標準出力されたときに、passwordを自動入力する
       local cmd=$1
       local password=$2
       echo $cmd
       expect -c "
                spawn ${cmd}
                expect {
                  "*password:*" { send $password\r\n; interact }
                  eof { exit }
                }
                exit
                           "
  • expectはtimeoutがあり、処理時間の長い対話形式のシェルやコマンドだと問題がある。以下のようにtimoeut値(単位は秒。-1はtimeoutなし。)を設定できる。
       expect -c "
                set timeout -1
                spawn ${cmd}
                expect {
                  "*password:*" { send $password\r\n; interact }
                  eof { exit }
                }
                exit
                           "

数字を3桁カンマ区切りにする

echo 10000000 | awk '{printf "%\047d\n",$1}'
10,000,000

乱数取得

  • bashおよびzshにはRANDOM変数が用意されている。0~32767までの整数がランダムに格納される
    echo $RANDOM
  • 0~100までの値をとりたい場合
    echo $(($RANDOM % 101))

dateコマンド


ミリ秒、マイクロ秒、ナノ秒の取得

  • %Nでナノ秒が取得できる。また、%<桁>Nで表示桁数が設定できるので、各秒の表示は以下のように可能
  • ミリ秒
    date +'%Y%m%d-%H%M%S.%3N'
  • マイクロ秒
    date +'%Y%m%d-%H%M%S.%6N'
  • ナノ秒
    date +'%Y%m%d-%H%M%S.%N'

シェルでの時刻計算

  • 以下のようにepocタイムを取得すると楽
    [myapp@app-system ~]$ date
    Sun Feb 16 21:40:50 CST 2014
    [myapp@app-system ~]$ date +'%s'
    1392608453
    [myapp@app-system ~]$ date  -d "Sun Feb 16 21:40:50" +'%s'
    1392608450

簡単なforループの書き方

[ptestuser@testserver18]$ for i in {1..10}; do; echo $i; done 
1
2
3
4
5
6
7
8
9
10

改行削除・置換

カンマを改行に変更

  • trで
    tr ',' '\n' < filename 
  • sedで
    sed ':loop; N; $!b loop; ;s/,/\n/g' filename

改行削除

tr -d '\n' < filename 

if文

複数条件、ネスト

  • 同レベルの比較条件であれば-a(AND)、-o(OR)が使える
    if [ 1 -eq 1 -a 0 -eq 0 ]; then
       echo true
    fi
  • 条件A && (条件B || 条件C) のようなケースは以下で対応可能
    if [ 1 -eq 1 ] && [ "A" = "C" -o "B" = "B" ]; then
        echo true
    fi
  • 条件A && (条件B && (条件C || 条件D) )のようにネストが必要な場合は[[]]演算子を利用する。
    これはC言語などの一般的な条件式が利用できるので、万能
    if [[ ( A == 1) && ( B == 2 && ( C == 3 || D == 4 ) ) ]]; then
        echo true
    fi

少数比較

  • http://blogs.yahoo.co.jp/eguchium/50769722.html
    #!/bin/sh
    
    A=0.1
    B=0.2
    X=`echo "$A > $B" | bc` ### $A > $B が真なら 1、 偽なら 0
    if [ $X -eq 1 ] ;then     ###bashは真は0、偽は1なのでややこしい。
      echo $A is large
    else
      echo $B is large
    fi

文字列の長さ

  • 変数に格納せずとも、exprコマンドのlenthオプションで取得できる
    [root@testserver18 TAR_ABIS]# expr length "1234567890"
    10

awk

各種セパレータ設定 (FS, RS, OFS, ORS)

  • 参考
  • 以下、正式名称は予想。
  • FS
    • Field Separator。カラムの区切り文字を設定する。-Fオプションと同じ。以下は同じ動きをする。
      echo "a,b,c" | awk -F"," '{print $1, $2, $3}'
      echo "a,b,c" | awk 'BEGIN {FS=","} {print $1, $2, $3}'
      
      # 出力結果
      a b c
  • RS
    • Record Separator。レコードの区切り文字を設定する。改行以外でレコードを区切りたいときに設定
      echo "a,b,c" | awk 'BEGIN {RS=","} {print $1, $2, $3}'
      
      # 出力結果
      a
      b
      c
  • OFS
    • Output Field Separator。出力のカラムの区切り文字を設定する。
      echo "a b c" | awk 'BEGIN {OFS=","} {print $1, $2, $3}'
      
      # 出力結果
      a,b,c
  • ORS
    • Output Field Separator。出力のカラムの区切り文字を設定する。
      echo "a
      b
      c" | awk 'BEGIN {ORS="\n\n"} {print $1, $2, $3}' # レコードの区切りを改行2個
      
      # 出力結果
      a
      
      b
      
      c

GMT => JST変換 (文字列 => 時刻 変換処理含む)

  • mktime関数と strftime関数を利用する。これらは本来はgawkの機能らしい。
  • 下記例は、GMT時刻をJSTに変換する例。awkを利用しているが、RHEL(awk)のバージョンによってはgawkではないと動作しなかも。
    echo "10/28/2016 02:27:59" |
        awk '{
            split($1, date, "/")
            split($2, time, ":")
            tstamp = sprintf("%s %s %s %s %s %s", date[3], date[1], date[2], time[1], time[2], time[3])
            utime_jtc = mktime(tstamp) + 9 * 3600
            print (strftime("%Y/%m/%d %H:%M:%S", utime_jtc), $3)
        }'
    
    • 結果
      2016/10/28 11:27:59

awkにbashの変数を使用する

  • 変数をシングルクォートでくくる
    #!/bin/bash
    
    a=10
    echo "aaa" | awk '{print $1, '$a'}'
    
    [ptestuser@testserver18]$ sh  test.sh
    aaa 10
  • RHEL6.3(GNU Awk 3.1.7)では、上記やり方でできなかったが、-vオプションで変数設定が可能。
    a=10
    b=20
    echo "aaa" | awk -v val=$a -v val2=$b '{print $1, val}'

awkで数時を3桁区切りにする

  • \047を使う。
    shishimaru@myserver% echo 123456789 | awk '{printf("%\047d\n", $1)}'
    123,456,789

rpm

rpmインストール・アンインストール前後に実行されるスクリプトの確認方法

  • コマンド
    rpm -q --scripts <PKG_NAME>
    rpm -qp --scripts <PKG_FILE>
    • preinstallと定義されているのが、install前
    • postinstallはinstall後
    • preuninstall, postuninstallもある

rpmでハングしたとき

  • rpm -e <TEST-PKG>でハングする現象が起きた。

DB rebuild

  • よくある話はrpmデータベースの再作成での解消方法
    • http://www.tec-q.com/note/2007/04/rpm.html
      • 1. 破損したデータベースのバックアップ
        cd /var/lib/rpm
        cp -p __db.00* /tmp
      • 2. 破損したデータベースの削除
        rm -f __db.00*
      • 3. RPM データベースの修復
        rpm --rebuilddb
      • 以上

ファイルシステムのチェックスキップ

  • 東京環境で直面したときは上記方法では直らず。rpm -e時にはmountしたファイルシステムのチェックを行うが、本環境では、testserver12:/shareをnfsマウントしており、nfsサーバがなぜか参照できなくなっていた。
  • 以下のコマンドでファイルシステムチェックをスキップするとpkg削除できた。
    rpm -e --ignoresize test-unit
    

rpmからファイルを抽出

  • 全ファイルを抽出
    rpm2cpio test-unit-3.2.3-0.x86_64.rpm | cpio -id
  • list表示
    rpm2cpio test-unit-3.2.3-0.x86_64.rpm | cpio --list

top

topの結果をファイル出力

  • 下記のオプションでテキストベースになる
    top -b -c -d1 -n3 > top.log
    • -b : batchモード
    • -c : コマンドの引数も表示する
    • -d : interval
    • -n : 表示回数

topコマンドの出力値のデフォルト設定

  • /etc/toprcまたは、$HOME/.toprcファイルに設定を記述することでデフォルトの出力情報を変更できる。(初期状態はこれらのファイルは存在しない)
  • $HOME/.toprcはtopコマンド実行中に、Shit+wを押すことで現在の出力状態をデフォルトとして作成される。
  • 下記は、toprcの中身だが、書式はよくわからない。
    top's Config File (Linux processes with windows)
    Id:i, Mode_altscr=0, Mode_irixps=1, Delay_time=1.0, Curwin=0
    Def    fieldscur=\¨&#179;´&#187;&#189;&#192;&#196;&#183;&#186;&#185;&#197;&')*+,-./0125¶8<>?ABCFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
           winflags=193972, sortindx=18, maxtasks=0, graph_cpus=0, graph_mems=0
           summclr=1, msgsclr=1, headclr=3, taskclr=1
    Job    fieldscur=\&#166;&#185;&#183;&#186;(&#179;´&#196;&#187;&#189;@<§&#197;)*+,-./012568>?ABCFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
           winflags=193844, sortindx=0, maxtasks=0, graph_cpus=0, graph_mems=0
           summclr=6, msgsclr=6, headclr=7, taskclr=6
    Mem    fieldscur=\&#186;&#187;<&#189;&#190;&#191;&#192;&#193;MBN&#195;D34&#183;&#197;&'()*+,-./0125689FGHIJKLOPQRSTUVWXYZ[\]^_`abcdefghij
           winflags=193844, sortindx=21, maxtasks=0, graph_cpus=0, graph_mems=0
           summclr=5, msgsclr=5, headclr=4, taskclr=5
    Usr    fieldscur=\&#166;§¨&#170;°&#185;&#183;&#186;&#196;&#197;)+,-./1234568;<=>?@ABCFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
           winflags=193844, sortindx=3, maxtasks=0, graph_cpus=0, graph_mems=0
           summclr=3, msgsclr=3, headclr=2, taskclr=3
    Fixed_widest=0, Summ_mscale=0, Task_mscale=0, Zero_suppress=0

pgrepで二重起動を防ぐ。

printf

  • 文字列整形
    shishimaru@myserver% printf "%5d %d %d\n" 10 200 3000                                                                           
    00010 200 3000

rsync

  • ディレクトリを再帰的にsyncかつ、受信側に既にファイルが存在していて、送信側のファイルよりも新しかった場合にはそのファイルのsyncをスキップ
    rsync -ur DIR root@myserver:/home/myapp/DATA_FILES
  • rsyncオプションいろいろ

Error出力を標準出力にしつつteeコマンド実行

  • Errorも標準出力+ファイル出力
    command 2>&1 | tee log

マイクロsec単位のスリープ

  • 以下で100msecのスリープ
    usleep 100000

mount

  • mount時、root以外もmount先にファイルを生成できるようにするには
    • mount /dev/sdb1 /mnt/usbdisk1/ -t vfat -o umask=000

bash

shebang(シバン)で設定できるオプション

#!/bin/bash -eux -o pipefail -o errtrace
  • -e
    • コマンドがエラーになった時点でスクリプトを強制終了
    • パイプで連結している場合は一番最後のコマンドで判定させる (-o pipefailが設定されている場合は、どのパイプでエラーになっても終了するようになる)
    • 「||」や「&&」を利用すると効果は消える
  • -u
    • 未定義変数が出現すると強制終了
  • -x
    • 実行ラインを標準出力
  • -o pipefail
    • -eで、パイプ連結しているコマンドに対して、どのパイプでエラーになっても強制終了させる
  • -o errtrace ( or -E)

grep --line-buffered

  • tailコマンドでgrepを2重以上かけるときには以下のオプションを使う。
    tail -F server/default/log/tm-info.log | grep --line-buffered -v "PERF" | grep "EXTRACT" | sed 's/\[.*CATEGORY===INFO//g'

サイズの大きいファイルをscpしたときにETA lost connectionが出て途中で終わる

rsyncコマンドでコピーする

  • rsync -av --progress --inplace --rsh='ssh' somefile user@server:directory/

LDAP刺さり

  • rootでauthconfig-tuiコマンドうつ
  • Use LDAP Authenticationのチェックをはずす。

配列

  • 要素追加
    local list=()
    list+=("hoge")
  • 全コピー
    local NewArray=()
    local Array=()
Array+=("hoge1")
Array+=("hoge2")
NewArray=("${Array[@]}")
  • 関数の引数に渡す
    function func() {
      declare -n arg_list=$1
      for i in ${arg_list[@]}
      do
          echo $i
      done
    }
    
    list=("hoge" "huga")
    func list  # $をつけずに渡す
  • 全参照 [#y0a8cd0e]
    for i in ${list[@]}
    do

case文のパイプ

case文
case $val in
	true|false)

sed

指定した正規表現に該当する次の行に追記する

  • hoge2の次の行に追加
    $ sed '/hoge2/a hoge-add' text.txt
    
    hoge
    hoge1
    hoge2
    hoge-add
    hoge3
    hoge4

n個目〜n個目にヒットした箇所の置換

  • n個目の置換
    sed 's/before/after/n'
  • n個目〜最後までを置換
    sed 's/before/after/ng'
  • 最後からn個目まで以外を置換
    command | tac | rev | sed 's/before/after/ng' | tac | rev
  • n個目から4個置換(n個目含む)
    sed -e\ 's/before/after/n'{,,,}

指定文字列を含む行を置換

  • 「2:」を含む行の「:」を「@」に置換する例
    $ echo -e "1:aaaa\n2:bbbb\n3:cccc"
    1:aaaa
    2:bbbb
    3:cccc
    
    $ echo -e "1:aaaa\n2:bbbb\n3:cccc" | sed '/2:/s/:/@/'
    1:aaaa
    2@bbbb
    3:cccc

指定文字列を含まない行を置換

  • 「2:」を含まない行の「:」を「@」に置換する例
    $ echo -e "1:aaaa\n2:bbbb\n3:cccc"
    1:aaaa
    2:bbbb
    3:cccc
    
    $ echo -e "1:aaaa\n2:bbbb\n3:cccc" | sed '/2:/!s/:/@/'
    1@aaaa
    2:bbbb
    3@cccc

指定行取得

  • sed -n '10p' File
  • sed -n '2,10p' File

指定文字列に隣接する行も表示

  • -nオプションとpフラグの前に、マッチする行の前後の表示行数を指定する
    [appuser@testserver11 logs]$ sed -n "/ready to deSerial from inputStream../,+1p" catalina.out
    2016-08-24 16:16:12,075 [http-nio-8080-exec-13] DEBUG [appserver.InquiryServlet] - ready to deSerial from inputStream..
    2016-08-24 16:16:12,079 [http-nio-8080-exec-13] DEBUG [appserver.InquiryServlet] - ready to call inquiry with parameter PBTestJobRequest..
    2016-08-24 16:16:13,460 [http-nio-8080-exec-1] DEBUG [appserver.InquiryServlet] - ready to deSerial from inputStream..
    2016-08-24 16:16:13,464 [http-nio-8080-exec-1] DEBUG [appserver.InquiryServlet] - ready to call inquiry with parameter PBTestJobReque

指定文字列間の行取得

  • 文字列「ABC」から「EFG」の間の行を表示。(ABCとEFGも含む)
    • sed -n '/ABC/,/EFG/p' <FILE>

置換の範囲指定。

  • http://d.hatena.ne.jp/n9d/20081110/1226284188
  • 1回目にマッチングしたところだけ置換 [#pcd0bcaf]
    $ echo -e "aaaa\n bbbb\n  cccc\naaaa\n bbbb\naaaa\n bbbb" | sed 1,/bbbb/s/bbbb/dddd/
    aaaa
     dddd
      cccc
    aaaa
     bbbb
    aaaa
     bbbb
  • ccccとbbbbに囲まれた中でのみaaaa->eeeeという置換
    $ echo -e "aaaa\n bbbb\n  cccc\naaaa\n bbbb\naaaa\n bbbb" | sed /cccc/,/bbbb/s/aaaa/eeee/
    aaaa
     bbbb
      cccc
    eeee
     bbbb
    aaaa
     bbbb
  • 範囲指定のネスト。下記は<KEYWORD>〜</KEYWORD>の間の、<rolled>〜</rolled>の間にある、enrollという文字列をidentifyに変えるケース
    cat config/parameter.xml | sed '/<KEYWORD>/,/<\/KEYWORD>/{/<rolled>/,/<\/rolled>/s/enroll/identify/}'
  • ネストのネストも可能
    cat config/parameter.xml | \
           sed '/<KEYWORD>/,/<\/KEYWORD>/{/<extract>/,/<\/extract>/{/<rolled>/,/<\/rolled>/s/enroll/ennnn/}}' | grep enn

指定行削除

sed -e '開始行,削除行d' ファイル名