カテゴリー別アーカイブ: Linux

waitコマンドとLinux/Unixの仕様メモ

バックグラウンドプロセス2個以上をwaitして実行結果を取得する際のメモです。Linux(RHEL7、CentOS7)で確認済みですが、Unixでも同じことが言えるはずです。

まずは間違っている例です。下記の様に、プロセスIDを取得して、waitする際に2個のプロセスIDを指定してしまうと、誤った結果が得られます。(最後に指定したプロセスIDの結果のみが取得できる。)

$ cat parent.sh
#!/bin/sh

./child.sh $1 &
child1p=$!

./child.sh $2 &
child2p=$!

sleep 3

wait $child1p $child2p
childr=$?

echo "child1p:$child1p, child2p:$child2p, childr:$childr"
$ cat child.sh
#!/bin/sh
sleep 1
exit $1

下記が実行結果です。

$ time ./parent.sh 1 1
child1p:10159, child2p:10160, childr:1

real    0m3.005s
user    0m0.005s
sys     0m0.003s
$ time ./parent.sh 0 0
child1p:10165, child2p:10166, childr:0

real    0m3.005s
user    0m0.004s
sys     0m0.003s
$ time ./parent.sh 0 1
child1p:10171, child2p:10172, childr:1

real    0m3.005s
user    0m0.001s
sys     0m0.006s

うまくいくやん! と、思われ方。要注意! 下記を実行すれば誤りに気付きます。

$ time ./parent.sh 1 0
child1p:10152, child2p:10153, childr:0

real    0m3.006s
user    0m0.004s
sys     0m0.004s

引数pidを指定した場合,waitコマンドは最後の完了を待ったプロセスのコマンドの終了コードで終了します。
http://itdoc.hitachi.co.jp/manuals/3021/3021313320/JPAS0399.HTM

正しい実装は下記。それぞれ個別にwaitする必要がある。

$ cat parent2.sh
#!/bin/sh

./child.sh 0 &
child1p=$!

./child.sh 1 &
child2p=$!

sleep 3

wait $child1p
child1r=$?

wait $child2p
child2r=$?

echo "child1p:$child1p, child2p:$child2p, child1r:$child1r, child2r:$child2r"

ん? と思われた方。そう、子プロセスが[sleep 1]しているにも関わらず、親プロセスで[sleep 3]しているため、waitにたどり着くまでに子プロセスは終了しています。「終了したプロセスの実行結果なんて取得できるの?」と。

waitコマンドについて詳しく言及した記事はありませんでしたが、waitシステムコールについて調べると、ありました。

終了したが、wait されていない子プロセスは「ゾンビ」になる。後で親プロセスが wait を実行して子プロセスについての情報を取得できるように、カーネルはゾンビプロセスについて最小限の情報 (PID、終了ステータス、 リソース使用状況) を保持する。ゾンビプロセスは、 wait によってシステムから削除されない限り、カーネルのプロセステーブルの 1 エントリーを消費する。このプロセステーブルが 一杯になると、新たにプロセスを作ることができなくなる。親プロセスが終了すると、その親プロセスの「ゾンビ」の子プロセスは (もしあれば) init(1) の養子となる。 init(1) は wait を自動的に実行し、ゾンビを削除する。
https://linuxjm.osdn.jp/html/LDP_man-pages/man2/wait.2.html

「養子」という表現、かなり好きです。(そこではない) つまり、子プロセスの実行結果は、親プロセスが終了するまでは間違いなく保持されるようです。そのため、上記コードは問題なしです。

$ time ./parent2.sh 1 0
child1p:10185, child2p:10186, child1r:0, child2r:1

real    0m3.006s
user    0m0.004s
sys     0m0.004s

位置100で不正な入力シーケンス(iconv)

「位置xxxで不正な入力シーケンス」は、いわゆるWindowsの機種依存文字である丸付き数字等をiconvでutf8→shift_jisに変換しようとしたときに発生するエラー。丸付き数字は、utf8では規定されているが、厳格なshift_jisには存在しないので、対応する文字無しということでエラーになる。(Windowsにおけるshift_jisは、正確にはshift_jisではなく、暗黙的に拡張文字(丸付き数字等)を含んだもの。)拡張文字も含んだcp932を変換後の文字コードとして指定してやれば解消する。

下記は、Linux上の日本語名ファイルを、zip等に纏めてftpでWindowsに送ったときに文字化けさせない対応のサンプルスクリプト。(utf8→cp932に変換している。)

mv "$filename" "`echo $filename | iconv -f utf8 -t cp932`"

意地でも正規表現を使わずに拡張子とファイル名を分割する

良く使うのに毎回調べている気がする正規表現をあえて意地でも使用せずに、拡張子(.tar.gz)とファイル名を分割する。ファイルのバックアップをアンダースコア+日付でcpして取得する場合が良くあるが、そういったときのアンダースコア分割にも応用できる。特にアンダースコアはファイル名自体にも使用されることがあるので、revで逆さにしてから変換する。(セパレータが何個存在するか分からないので、逆から処理するとうまくいく)

echo test.test.tar.gz | rev | cut -d '.' -f 3- | rev

逆にして、先頭からセパレータを探索、3個目以降を取得、再度逆にする。言われてみるとシンプルだけれど、なかなか出来なかった発想。