ログインしてさらにmixiを楽しもう

コメントを投稿して情報交換!
更新通知を受け取って、最新情報をゲット!

#!/bin/shコミュのタブ区切りヒアドキュメントを配列に入れたいがタブが消えてしまう

  • mixiチェック
  • このエントリーをはてなブックマークに追加
ヒアドキュメントをタブ区切りで書き、
ループでいったん配列に入れた後、再びループ中でタブ区切り列を取り出したいのですが、
ヒアドキュメントを読み込んだ時点でタブが消えてしまうようです。
これはUNIXのヒアドキュメントの仕様なのでしょうか?

先ずお客様の要件として、
・複数のコマンド実行結果をファイルにリダイレクト
・個々のコマンド実行後のエラー時にif文で処理をハンドリング
・エラーハンドリングは全てのコマンドで同じ処理
というものがあります。

個々のコマンドをベタ打ちで、コマンド数分if文を書きたくはないので、
以下のように“コマンド”,“結果出力ファイル名”をヒアドキュメントで書いておき、
ループ中で1行づつ配列に入れた後、またループ中で1行づつ、
awk -F \t '{print $1}'、awk -F \t '{print $2}'
で取り出してコマンド実行させ、
エラー時は同じエラーハンドリング、とやりたいのですが、
ヒアドキュメントからCMDLINEに取り込んだ時点でタブが消えてしまうようです。

画面で見るだけではなく、
echo ARR[2] > ARR.txt
で吐いて、odで見た時点で消えてます。

\でエスケープしたり、デリミタからの入力リダイレクト時に“-”を付けてみたり、
等は試してみました。

列セパレータをカンマ等にすれば最終目的は達せられると思うのですが、
好みの問題ですが、出来ればタブ区切りレコードでやりたいのです。
何か回避策はないものでしょうか?

#やりたい事
i=0
while read CMDLINE; do
ARR[i]=$CMDLINE
i=`expr $i + 1`
done <<-_CMDDEF_
"コマンド1"\ コマンド1結果.txt
"コマンド2 -オプション"\ コマンド2結果.txt
"コマンド3 引数"\ コマンド3結果.txt
"コマンド4 -オプション 引数"\ コマンド4結果.txt
#以下コマンド沢山!
_CMDDEF_

for i in ${ARR[@]}
do
CMD=`echo $ARR[i] | awk -F \t '{print $1}'`
STDOUT=`echo $ARR[i] | awk -F \t '{print $2}'`
$CMD > $STDOUT
if [ $? -ne 0 ]
#エラーハンドリング1
elif [ -z $STDOUT ]
#エラーハンドリング2
fi
done

#######################################################
#この投稿は解決を急いでおり、同内容を他コミュニティに投稿させていただく予定です。
#投稿先での頂いたレスや解決した場合の状況報告など、責任をもってさせていただきますので、
#マルチポストではなく、いわゆるクロスポストとして、平にご容赦いただきたく願います。
#######################################################

コメント(26)

現在までのクロスポスト先を報告しておきます。
http://mixi.jp/view_bbs.pl?id=4456741&comm_id=209032
http://mixi.jp/view_bbs.pl?id=405757&comm_id=68577
http://mixi.jp/view_bbs.pl?id=9555552&comm_id=52870
http://mixi.jp/view_bbs.pl?id=9554085&comm_id=6295
http://mixi.jp/view_bbs.pl?id=9555242&comm_id=150915
です。
masa さんはじめまして。

私も bash は詳しくないのですが…(普段は zsh)
セパレータの切替えは $IFS への代入で行けるはずです。

あと、【実行のループで】タブ分割をするより、
【ヒアドキュメントを読むループ】でタブ分割して、
先にコマンド用と保存先用の二つの配列に分割する
方が楽な気はします。

declare -i i=0
declare -a CMDS LOGS
while IFS=$'\t' read cmd log
do
CMDS[i]="$cmd"
LOGS[i]="$log"
((i++))
done <<EOF
cd / foo.log
ls * bar.log
EOF

echo CMDS="${CMDS[*]}"
echo LOGS="${LOGS[*]}"

set -x
for ((i = 0; i < ${#CMDS}; i++));
do
echo cmd=${CMDS[i]}
echo log=${LOGS[i]}
done

ご参考まで…
>4: h小林@ぶりぶりっと さん

コメントありがとうございますです。

とりあえず現在の状況ですが、
客先へは取りあえずCSVで納品しましたので、お客さま(=現場の上司)の最終要望としては満たせた状況ではあります。

しかし技術的興味としては追いかけたいネタではありますので、
http://mixi.jp/view_bbs.pl?id=4456741&comm_id=209032
の方でいただいたコメントとも合わせ、まずはサンプルソースをよく読んで、来週現場のマシンで試してみます。

PS.
実はCSVで納品したソースを書いてる途中、別の疑問点が出てきてしまいました。
後日投稿させていただきます。
でもその前に本件であちこちからいただいたサンプルの検証をしないと失礼になっちゃうんでまずはソースを読みます。。
ええと、前半のヒアドキュメント内の \ はタブ文字の表現という意味ですよね。
前半は間違って無いですが、後半はぼろぼろです。

for i in "${ARR[@]}"
do
eval CMD=`echo "$i" | awk -F'\t' '{print $1}'`
STDOUT=`echo "$i" | awk -F'\t' '{print $2}'`
$CMD > $STDOUT
if [ $? -ne 0 ]
then
#エラーハンドリング1
elif [ -z $STDOUT ]
then
#エラーハンドリング2
fi
done

タブがセパレータならヒアドキュメントのコマンド部分を" "で囲む必要は特に無いでしょう。囲まないほうがいいと思います。その場合は、上記の3行目のevalは不要です。
マルチポストについては、
「コメントは他のコミュニティーにも私が転載します。転載不可の方はコメントをしないでください。」と
宣言した上で、コメントが投稿されるないなや(とまでは言わないが半日以内程度)に、他のコミュニティーにもコメントを転載すれば、クロスポスト相当になりますね。

ご自分でも
>投稿先での頂いたレスや解決した場合の状況報告など、責任をもってさせていただきますので、
と書いておられますし。

ただ、言うだけじゃなくて実行しなきゃ駄目。今後は気をつけてください。
本コミュを始め、他コミュの皆様ともども。。
返事遅くなりまして失礼しました。
他の納品も忙しくてテンヤワンヤしてまして…MvvM

未だ、
別コミュでいただいた『IFS』に改行コードを入れる方針
を試してみた段階
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
"コマンド1"\ …タブ… コマンド1結果ファイル名

while read CMDLINE; do
でCMDLINEには読み込まれるのですが、
配列に代入する瞬間に文字列ではなくコマンドとして実行されてしまう、
という現象
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
でして。。

なので、otnさんのサンプルコードの箇所に至る以前で異常終了
(echo $?で見たところ、/bin/shは“1”を返してます)
してしまう状況です。

h小林@ぶりぶりっとさんのコードは未だ試してませんが、
2つの配列に代入する時点で1列目、2列目が分割済み、という方針ですか。。
これだとうまくいくかも。。

クロスポスト先の方に張りました当方のコードにつきましても、お気づきの点などございましたら、
ご指摘いただけると幸いです。


>とまでは言わないが半日以内程度
遅レスにもほどがあり過ぎでしたね。
今後はスケジュール的にヤバそうなときは気をつけます。

(とはいっても、なるべく大勢の人に相談したい、というのも人情だしなー)
(ピコーン!そうだ。トピ毎あるいはせめてコミュニティ毎に『閲覧リクエスト』や『書き込み』の度合いを計るレイティングみたいな機能があればいいんだ。最近の2chみたいに…)

どうでしょう?>バタラさん

(なんか板を銘柄に見立てて2ch証券市場とか、コテを馬に見立ててカキコに賭けをしたりとか、面白いこと始めてるみたいね…あくまでも擬似貨幣だろけど勿論)
PS.
#2006年08月19日01:39 5: masa
で述べた『別の疑問点』が
http://mixi.jp/view_bbs.pl?id=4456741&comm_id=209032
に書いた、改修2)でして、こちらは書きましたとおり解決済みです。
> All
大事なことを訂正し忘れておりました。
最初に、
環境:HP-UX
シェル:bash
と書きましたが、
環境:HP-UX11、Solaris8
シェル:Bourne-shell(/bin/sh)
に訂正します。

最初の納品物はHP-UXのbash用だったのですが、その後SolarisでもBourne-shellでも動かす要件が上がり、
納品物は両OSの/bin/shで動作検証したものを納品しております。
本投稿の検証は現在はSolarisで行っております。



> h小林@ぶりぶりっとさん
> 【ヒアドキュメントを読むループ】でタブ分割
> while IFS=$'\t' read cmd log
でやってみましたが、
(B-shellなのでdeclareは効かないのでexprで)
何故か、
コマンドのオプションや引数に“t”が含まれる場合にフィールド区切りとして認識されてしまう
ようです。

IFS=$'t'
IFS=$'\\t'
IFS=$'\\\t'
とやってみましたが同じでした。(上2つは当然でしょうけれども)

具体的にはヒアドキュメントに、
ls -lRatr . …タブ×5… ls
としたら、
CMDS[0]=ls -lRa
LOGS[0]=r .
が代入される動きとなってしまいました。



そしてやはり同様に(クロスポスト先で報告済みのとおり)代入の途中で、
落ちました。($?が“1”でスクリプト終了)

以下は /bin/sh -vx test.sh の結果

+ CMDS[0]=ls -lRa
test.sh: CMDS[0]=ls -lRa: not found
+ LOGS[0]=r . …タブ×5… ls
test.sh: LOGS[0]=r .^I^I^I^I^Ils: not found
test.sh: bad substitution



また、『UNIXシェルスクリプト逆引き大全333の極意』という本に、
「“\”付きファイルからreadに読み込ませる場合は“read -r”とする必要がある」
というようなことが書いてありました。
IFSは読み込ませるファイルではありませんが、ものは試しと、
−−−−−−−−−−−−−−−−−−−−
READ=/usr/xpg4/bin/read
IFS=$'\t' # IFS="\t"でも同じ結果だった
while ${READ} -r CMD LOG; do
−−−−−−−−−−−−−−−−−−−−
としたところ、今度は、

+ /usr/xpg4/bin/read -r CMD LOG
+ echo
+ CMDS[0]=
test.sh: CMDS[0]=: not found
+ echo
+ LOGS[0]=
test.sh: LOGS[0]=: not found
test.sh: bad substitution

となって落ちました。



文字列がコマンドとして実行されて(コマンド置換)いることが問題かも?
と思いまして、以下のコードで試したところ、

test2.sh
−−−−−−−−−−−−−−−−−−−−
#!/bin/sh
i=0

cmd="aaa"

while [ $i -lt 3 ]; do
arr[$i]=`echo "$cmd"_"$i"`
i=`expr $i + 1`
echo $arr[i] "in the loop"
done

echo $arr[0] "out of the loop"
echo $arr[1] "out of the loop"
echo $arr[2] "out of the loop"
echo $arr[3] "out of the loop"

exit 0
−−−−−−−−−−−−−−−−−−−−
配列への代入箇所で
test2.sh: arr[0]=aaa_0: not found
にはなりますが、落ちはしませんでした。



そこで、ヒアドキュメントをCSVに戻し、
IFS=$','
でやってみたところ、なんと!!
ちゃんと、
test.sh: CMDS[0]=ls -lRatr .: not found
test.sh: LOGS[0]=ls: not found
と入った
(“not found”は問題ではないことは、test2.shで判明している)
のに
test.sh: bad substitution
となって落ちたのです!!
(納品物は配列は使ってない)



色々調べたところ、自分でデバッグ用にループ中に、

echo "${CMDS[${i}]}"

としていた箇所があったのですが、
どうやらそこで“bad substitution”で落ちているようです。
コメントアウトしたらループを抜けましたので。。

ただしその後のコードは前半の修正に合わせてないままだったので、
落ちましたが当然です。
ループを抜けた、という点までの確認となります。



以下は上記箇所を色々と変えてみた結果(/bin/sh -vx test.sh の)です。

echo $"{CMDS[${i}]}"
 ↓
{CMDS[0]}

eval echo $"{CMDS[${i}]}"
 ↓
{CMDS[0]}

eval echo $"$CMDS[${i}]"
 ↓
[0]

となり、目的の文字列“ls -lRat”が取り出せません。



B-shellで配列を、しかもループ中で使うと、いろいろと面倒なことが起きるみたいです(T_T

しかしながら、ループ内で配列との変数受渡しには、
1行中に“$”が複数現れるコーディングはどうしても不可避なので、
なんとかクリヤーしたいところです。

現場がいまちょうど落ち着いてる状況なので、もうちょっと頑張ってみて、経過報告します。



>2006年09月12日08:44 10: t2さん
コメント多謝!多謝!です。
今ちょうどデスマが収束したところなので、今のうちに現場のSolaris(SunOS 5.8です)で試してみます。
$'...' 記法や配列は、bashの拡張機能で、Bourne-shellにはありません。
下記でBourne-shellでも動くはずです。ただし、引数 $1, $2, ... を破壊します。
なお、ヒアドキュメントからは先に書いたように" "を除いています。<TAB>のところにはもちろん実際にはタブコードを入れてください。

set dummy
while read CMDLINE; do
set "$@" "$CMDLINE"
done <<-_CMDDEF_
コマンド1<TAB>コマンド1結果.txt
コマンド2 -オプション<TAB>コマンド2結果.txt
コマンド3 引数<TAB>コマンド3結果.txt
コマンド4 -オプション 引数<TAB>コマンド4結果.txt
#以下コマンド沢山!
_CMDDEF_

shift
for i
do
CMD=`echo "$i" | awk -F'\t' '{print $1}'`
STDOUT=`echo "$i" | awk -F'\t' '{print $2}'`
$CMD > $STDOUT
if [ $? -ne 0 ]
then
#エラーハンドリング1
elif [ ! -s $STDOUT ]
then
#エラーハンドリング2
fi
done
上のスクリプトは、元のものに近くなるように書きましたが、私がゼロから書くなら、こういう方針で行きます。

awk -F'\t' '{
print $1,">",$2
print "if test $? -ne 0"
print "then echo エラーハンドリング1"
print "elif test ! -s",$2
print "then echo エラーハンドリング2"
print "fi"}' <<-_CMDDEF_ | sh
コマンド1<TAB>コマンド1結果.txt
コマンド2 -オプション<TAB>コマンド2結果.txt
コマンド3 引数<TAB>コマンド3結果.txt
コマンド4 -オプション 引数<TAB>コマンド4結果.txt
#以下コマンド沢山!
_CMDDEF_
12の解説を書き忘れましたが、Bourne-shellでストレートに配列として使えるのは、$1,$2,... しかないので、set を使ってそれらに値をセットし、
for i
でそれらを順に処理します。(in部を省略)

set A B C は、$1=A; $2=B; $3=C というような意味です。実際には、$1には直接代入できないのでsetを使います。
"$@"の意味はman shを見てください。

また、今回はsetで用足りたので使いませんでしたが、Bourne-shellで配列を使うのは、
ARRAY$N=.....
eval echo \$ARRAY$N
のようにします。eval についてもman sh参照。
コメントありがとうございます。

【現状報告】
今日はちょっと仕事ができて、本件に割く時間があまりとれませんでした。

ただいま、教えていただいた、
『ヒアドキュメントから読み込むときに、コマンド用とファイル用の2つの配列に入れておく』
というやり方を試しているところです。TSVの前にCSVで試してます。

既にご報告してますとおり、2つの配列に入ったように見えはするのですが、
直後に取り出そうとすると取れないのです。

−−−−−−−−−−ソース(部分)−−−−−−−−−−
exec 3<&0 <<-_ENDOFCMD_
ls -lRatr .,ls
ngcmd hogehoge -p foo bar,ngcmd
/usr/xpg4/bin/id -p,id
_ENDOFCMD_

i=0

while IFS=$',' read CMD LOG; do
echo "i = " ${i}
CMDS[$i]='"$CMD"'
LOGS[$i]="$LOG"

i=`expr ${i} + 1`

eval echo \$CMDS[$i] "---test1---"

eval echo "$CMDS[$i]" "---test2---"
echo "$"$CMDS[$i]"" "---test3---"
eval echo "$"$CMDS[$i]"" "---test4---"
eval echo $'$CMDS[$i]' "---test5---"
echo $'$CMDS[`echo $i`]' "---test6---"
eval echo $"$CMDS[$i]" "---test7---"
eval echo "$"$CMDS[$i]"" "---test8---"
eval echo "$"$CMDS[$i]"" "---test9---"
echo "$CMDS[$i]" "---test10---"
eval echo $"$CMDS[$i]" "---test11---"
echo $CMDS[$i] "---test12---"
eval echo $'$LOGS[$i]' "---test13---"
eval "echo \$CMDS[$i]" "---test14---"
eval "echo "$""CMDS[`echo $i]`"" "---test15---"
CMDO="CMDO=\$$CMDS[$i]" ; eval $CMDO ; echo $CMDO "---test16---"
done
exec 0<&3 3<&-


−−−−−−−−−−/bin/sh -vxで見たループ中の結果(部分)−−−−−−−−−−
+ CMDS[2]=/usr/xpg4/bin/id -p
test.sh: CMDS[2]=/usr/xpg4/bin/id -p: not found
+ LOGS[2]=id
test.sh: LOGS[2]=id: not found

(※)
このように配列に代入はされる
(“not found”と出てるのは問題ないらしい)
のですが…

+ eval echo $CMDS[3] ---test1---
+ echo [3] ---test1---
[3] ---test1---

+ eval echo [3] ---test2---
+ echo [3] ---test2---
[3] ---test2---

+ echo $[3] ---test3---
$[3] ---test3---

+ eval echo $[3] ---test4---
+ echo [3] ---test4---
[3] ---test4---

+ eval echo $CMDS[$i] ---test5---
+ echo [3] ---test5---
[3] ---test5---

+ echo $CMDS[`echo $i`] ---test6---
$CMDS[`echo $i`] ---test6---

+ eval echo [3] ---test7---
+ echo [3] ---test7---
[3] ---test7---

+ eval echo $[3] ---test8---
+ echo [3] ---test8---
[3] ---test8---

+ eval echo $[3] ---test9---
+ echo [3] ---test9---
[3] ---test9---

+ echo [3] ---test10---
[3] ---test10---

+ eval echo [3] ---test11---
+ echo [3] ---test11---
[3] ---test11---

+ echo [3] ---test12---
[3] ---test12---

+ eval echo $LOGS[$i] ---test13---
+ echo [3] ---test13---
[3] ---test13---

+ eval echo $CMDS[3] ---test14---
+ echo [3] ---test14---
[3] ---test14---
+ echo 3
+ eval echo CMDS[3] ---test15---
+ echo CMDS[3] ---test15---
CMDS[3] ---test15---
CMDO=CMDO=$[3]
+ eval CMDO= [3]
+ [3]
test.sh: [3]: not found (※)これは何だろう???
+ echo CMDO= [3] ---test16---
CMDO= [3] ---test16---

IFS=$,
+ read CMD LOG
exec 0<&3 3<&-
+ exec
−−−−−−−−−−−−−−−−−−−−
となってしまい、“/usr/xpg4/bin/id -p”が取り出せません(T_T;



【代入したループ中で配列取り出しの謎】
ひょっとして、
execでファイルハンドラを切り替えてループ中で配列に代入した場合は、
同じループ中(ファイルハンドラを戻す前の)では取り出すことはできない、
ということなのでしょうか??
exec 0<&3 3<&- でファイルハンドラを戻した後に、別のループで取り出さないとダメとか?
set -vxで見たときの、
+ CMDS[2]=/usr/xpg4/bin/id -p
test.sh: CMDS[2]=/usr/xpg4/bin/id -p: not found
+ LOGS[2]=id
test.sh: LOGS[2]=id: not found
は、あくまで、そういう代入式が走りました、というだけであり、
実際のメモリー上の変数に確定されるのは
done
の後だから、その前に取り出そうとしても取れない、とか??



【ループ後で配列取り出しの質問】
また、ループ後の全配列取り出しにつきましては、
ご教授いただいたコードを基に試してるのですが、うまくいきません。。

−−−−−−−−−−ループを抜けた後ソース(部分)−−−−−−−−−−
####以下は1つづつ試行###
echo CMDS="${CMDS[*]}" ---test101---
echo CMDS="${CMDS[@]}" ---test102---

echo "${CMDS[@]}" ---test104---
echo "${CMDS[*]}" ---test105---
eval "echo \"${CMDS[*]}\" ---test106---
eval echo "${CMDS[*]}" ---test107---
echo "${LOGS[*]}" ---test108---

−−−−−−−−−−ループ後の結果(部分)−−−−−−−−−−
echo CMDS="${CMDS[*]}" ---test101---
getInfo4nbuNG.sh: bad substitution (※)落ちた!!

echo CMDS="${CMDS[@]}" ---test102---
getInfo4nbuNG.sh: bad substitution (※)落ちた!!

echo "${CMDS[@]}" ---test104---
getInfo4nbuNG.sh: bad substitution (※)落ちた!!

echo "${CMDS[*]}" ---test105---
getInfo4nbuNG.sh: bad substitution (※)落ちた!!

eval "echo \"${CMDS[*]}\" ---test106---
eval echo "${CMDS[*]}" ---test107---
echo "${LOGS[*]}" ---test108---
 (※)この3つは抜けた!!



>otnさん、 t2さん
【ご教授いただいた内容への報告】
まず上記の配列からの取り出しの件をクリアーしてから、
ご教授いただいた、タブが消えてしまう原因への対応
for i in "${ARR[@]}"
do
eval CMD=`echo "$i" | awk -F'\t' '{print $1}'`
STDOUT=`echo "$i" | awk -F'\t' '{print $2}'`
$CMD > $STDOUT

を試したいと思います。

…が、このソースでまたまた一つ(配列クリアの先に)教えて下さいです。
1列目(コマンド文字列部分)を取り出す箇所にだけevalが付いているのは何故ですか?


#2006年09月13日02:48
以降のご提供いただいているソースも明日以降試して見ます。


以上のような状況です。
皆さま、状況報告は今しばらくお待ちください。
12に書きましたが、Bourne-shellに配列はありません。
したがって、私がbash前提で6に書いた
for i in "${ARR[@]}"
は、Bourne-shellでは動きません。

evalを使うと配列に似た機能(変数名を動的に生成)が使えますが、配列ではなくあくまで変数名に数字の付いた単純変数の集まりです。ちゃんとしたshの知識が無くてevalを使うのは無理でしょう。

どうしてもevalを使いたいなら、man sh を印刷して通読することです。evalの記述の所だけ読んでも意味が分からないと思います。

shの勉強はさておき、問題の解決優先で考えるなら、13に書いたものが簡単で分かりやすいと思いますが。シェルスクリプトを動的に作り出して実行する方法は応用も効きます。

>1列目(コマンド文字列部分)を取り出す箇所にだけevalが付いているのは何故ですか?

ここにevalの必要な理由は6に書いたので、どのようにわからないのか書いてもらわないと同じ説明しか出来ません。evalとはそもそも何者なのかを考えれば分かると思いますが。

P.S.
HTMLが使えれば、「Bourne-shellに配列はありません」のところを赤色・太字にしたいところです。
このネタは、何時ひとつのトピにまとまるのでしょうか?
ポスト先を皆がタブで開いて判断しないといけないのだろうか?

いい加減、それぞれのポスト先が毎回上がってうざい!
なるほど!うわ根本的なところで間違ってましたか。。
最初スクリプト1行目のシェル定義文(なんていうんでしたっけ)に
#!/bin/bash
で作りだしてたもんで、そのままARR[i]のやり方で通そうとしてしまってました。
(途中から現場の要望に合わせて
#!/bin/sh
に変えた)


>2006年09月13日23:48 17: 単細胞 2.0さま
クロスポスト先各所で同じ茶々入れを繰り返されてますが、そのご発言内容は、
私の投稿にコメントを付けて下さった方々、あるいはこれからコメントしようと思って下さっておられる方々への、強要強制に該当すると判断せざるを得ません。
それぞれのポスト先は今や同一内容ではない(枝分かれ派生している)のですから問題があるとは思えません…
が、しかし、
他トピではあまりコメントが付いていないのも事実なようですので、今後の当方の書き込みも(本件に関する限りは)本コミュ本トピに一本化したいと思います。
ここに一本化するということですので、別コミュからこっちに移動しました。

とりあえず私はFreeBSDな人なので、FreeBSDの/bin/sh (ash相当品)で動かすと言う前提で、以前別コミュで貼り付けた奴を改良して見ました。
POSIX準拠のshなら多分動くと思うのですが、手元にSolarisもHP-UXも無いので、それらのマシンで動くかどうかはわかりません。
配列を使わずに要件を満たしていると思うのですがどうでしょうか?

#!/bin/sh
# この掲示板ではタブがわかりにくいので;で代用しています。

exec << CMDLIST
a;
;b
c d
date;date.txt
ls -l;ls.txt
false;false.txt
true;true.txt
cut -d ' ' -f 2,3,6 date.txt;cut.txt
sh -c "exit 100";sh.txt
CMDLIST

IFS=";"
ERRORCOUNT=0

ERROR () {
echo "${ERRORMSG}"
ERRORCOUNT=$(( ${ERRORCOUNT} + 1 ))
}

while read CMD STDOUT
do
if [ -z ${CMD} ]
then
ERRORMSG="ERROR: null CMD in ${CMD};${STDOUT}"
ERROR
elif [ -z ${STDOUT} ]
ERRORMSG="ERROR: null STDOUT in ${CMD};${STDOUT}"
ERROR
else
eval "${CMD} > ${STDOUT}"
RET=$?
if [ ${RET} -ne 0 ]
then
ERRORMSG="ERROR: ${CMD} return ${RET}"
ERROR
elif [ ! -s ${STDOUT} ]
then
ERRORMSG="ERROR: ${STDOUT} is zero byte."
ERROR
fi
fi
done

echo "ERRORCOUNT = ${ERRORCOUNT}"
exit ${ERRORCOUNT}
データを全部読んでから実行を始めるという順序にこだわらず、また、実行するコマンドが、標準入力を読まないものだけなら、Flankerさんのお書きの、「読む端から実行」が楽ですね。
と思ったけど、標準入力がだめなのは私の13も同じだ orz

標準入力を読むコマンドがあるなら、12の方法か、
exec 3<<CMDLIST
.....
CMDLIST
...
while read CMD STDOUT <&3
do ...
done
のようにする。

細かいことを言うと、データの中にコマンド置換があると、「全部読んでから実行」と「読む端から実行」に違いが出ますが、普通は無いでしょうね。
皆様、ありがとうございます。
以下のコードで、
1)擬似的ではあるが配列で
(B-shellに配列が無いのを知らず、[]を使っててハマってた(T_T;)
2)いちおうタブ区切りで
動作確認とれました。
コメントアウトは試行錯誤した箇所でご参考までにとわざと残しておきました。

『いちおう』と書いたのは、やはりどうしてもタブで切り出すときに制御文字“\t”で認識してくれません。
データ中に“t”があると、そこで切りにいってしまいます。
“ls -lRatr .《タブ》ls”の場合、“ls -lRa”と“r .《空白》ls”で2列の配列の1行分に入ってしまいます。
IFS="《タブ》"にするとOKなのですが、やはりデータ(ヒアドキュメント)以外のコード中に実際の《タブ》を入れるのは僕的にはちょっと。。なんですよねー
プロンプトにコピペしてのワンライナーでの検証が出来ない(コマンドタイプで《タブ》を入力するのは不可能)というのが最大の理由ですね。。
好き嫌いの問題なんでしょうけど。。
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
exec 3<&0 <<-_ENDOFCMD_
ls -lRatr .《タブ》ls
ngcmd hogehoge -p foo bar《タブ》ngcmd
/usr/xpg4/bin/id -p《タブ》id
_ENDOFCMD_

i=0
#IFS="\n"
#IFS='
#'
#IFS=$'\n'

#while read CMDLINE; do
#while IFS=$'\t' read CMD LOG; do
#IFS=$'\t'
#IFS=$','
#IFS="\t"
#while read CMD LOG; do
#while IFS="\t" read CMD LOG; do
#while IFS=$',' read CMD LOG; do
#while IFS=$'\t' read CMD LOG; do
while IFS="《タブ》" read CMD LOG; do
#FS=$'\t'
#FS=$'《タブ》'
#CMD=`echo "$CMDLINE" | cut -d"$FS" -f 1`
#LOG=`echo "$CMDLINE" | cut -d"$FS" -f 2`

eval CMDS_$i='"$CMD"'
eval LOGS_$i='"$LOG"'

#CMDO="CMDO=\$$CMDS_$i" ; eval $CMDO ; echo $CMDO "---test---"

i=`expr ${i} + 1`
done
exec 0<&3 3<&-

j=0
exec 4<&0
#IFS=$'\n'
while [ $j -lt $i ]; do
#for CMDLINE ; do
#FS=$'\t'
#CMD=`echo $CMDLINE | cut -d"$FS" -f 1`
#LOG=`echo $CMDLINE | cut -d"$FS" -f 2`

eval echo \$CMDS_$j ---test$j.1---
eval echo '$CMDS'_$j ---test$j.2---

CMD=`eval echo '$CMDS'_$j`
LOG=`eval echo '$LOGS'_$j`

( eval $CMD >${DATEDIR}/${LOG}.txt ) 2>${DATEDIR}/${LOG}.err
RTRN=$?
…エラー処理…
done
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−


スクリプト内部からだとうまくいかないのに、プロンプトからコマンドラインで制御文字“\t”はうまくいきます。謎です。
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
# cat tsvdata
ata《タブ》btb
cnc《タブ》dnd

# cat csvdata
ata,btb
cnc,dnd

# cat tsvdata | cut -d $'\t' -f 2
btb
dnd

# cat csvdata | cut -d $',' -f 2
btb
dnd

# IFS=$'\t'
# export IFS
# cat tsvdata | cut -f 2
btb
dnd
−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
やや冗長な点はありますが、気に入ったコードが出来て何よりです。evalの副作用には注意が必要ですが。

これも前に書きましたが、Bourne-shell では、$'..' で \tや\nのように \ を使った記法(Cの文字列のような)はありません。これもbashの拡張機能です。

コマンドラインで $'\t' が使えたというのは、Bourne-shellじゃなくてbashを使っているのではないでしょうか。
bashでTAB文字が入らないのはTABは名前補完機能にバインドされているからです。ctrl-V TAB でできませんか?もしくは、bind "\C-i":self-insert で補完機能を解除する。

Bourne-shellだと普通の文字なのでTABもそのまま入ります。Bourne-shellのテストをするなら、コマンドラインも/bin/shでないと。その前に自分が使おうとしている言語(sh)の文法を知るのが第一歩ですが。
そうでした。
そういえばhistory機能とか補完機能が便利なんでコマンドラインは手で$bashと叩いてました。
コマンド自体にTABが入らないのはその為だったとは..不覚。

ログインすると、残り4件のコメントが見れるよ

mixiユーザー
ログインしてコメントしよう!

#!/bin/sh 更新情報

#!/bin/shのメンバーはこんなコミュニティにも参加しています

星印の数は、共通して参加しているメンバーが多いほど増えます。

人気コミュニティランキング