Yoh2の日記: シェルスクリプト (bash) で文字列分割 16
日記 by
Yoh2
[2012-03-09 23:35 str3に関する条件が紛らわしかったので補足しました]
久し振りにシェルスクリプト (bash) を書いていたら、もっと簡単に実現できそうだけどやり方を思い付かないというものが出てきたのでメモ。
やりたいこと:
シェル変数vに"str1:str2:str3"のようにコロン区切りされた3つの文字列が入っている時に、シェル変数aにstr1、シェル変数bにstr2、シェル変数cにstr3を格納したい。
その他条件は以下の通り。
- str1は空文字列の可能性がある。その時はaに空文字列を設定する。
- str3にコロンが含まれている可能性がある。その時はcにコロンも含めた文字列を設定する。(例: v="aaa:bbb:ccc:ddd"の時は、cは"ccc:ddd"となって欲しい(単に"ccc"ではなく))
- できる限り、シェルの機能と一般的なコマンド (目安としてbusyboxに入っているもの) で実現すること。
とりあえず以下のコードで実現できた。同じ文字列に対してcutを3回使っているのが何となく気に入らない。echoと組み合わせるのも何か大仰な感じがするし。
a="$(echo -n "$v" | cut -d: -f1)"
b="$(echo -n "$v" | cut -d: -f2)"
c="$(echo -n "$v" | cut -d: -f3-)"
${v%pat} とか ${v%%pat} とか ${v#pat}とか ${v##pat} (スコア:1)
a="${v%%:*}"
b="${v%:*}"
b="${b#*:}"
c="$}v##*:}"
みたいなの、kshと同様にbashでも使えませんでしたっけ? 変数に修められた文字列にマッチする最短/最長の先頭/末尾を切り取ったものが得られる文字列っぽいの。
真ん中を取り出すときが美しくないのはわたしがヘボいからです。
Re:${v%pat} とか ${v%%pat} とか ${v#pat}とか ${v##pat} (スコア:1)
アドバイスありがとうございます。
本文が誤解を招く表現になっていた (修正しました) ので、後出し気味の話になってしまいますが、v="aaa:bbb:ccc:ddd"のように、str3にコロンが入るとうまくいかないんですよね、これ。
巧妙に潜伏したバグは心霊現象と区別が付かない。
FizzBuzz並みなんだけど (スコア:0)
# 初期条件
v=aaa:bbb:ccc:ddd
# 初期化
c=$v
# 切り分け
a=${c%%:*}
c=${c#*:}
b=${c%%:*}
c=${c#*:}
# 結果出力
echo $a;echo $b;echo $c
ここまで示してもらってるんだから
このくらいアレンジしようよ
Re:FizzBuzz並みなんだけど (スコア:1)
あー、両脇から取り出すか先頭から順に取り出すかだけの違いじゃないか。
何故気付かなかったorz
巧妙に潜伏したバグは心霊現象と区別が付かない。
Re: (スコア:0)
cr='CAR:CADR:CADDR:CADDDR:CDDDDR'
d=''
while [ ${#d} -le 1 ] ; do
cmds='r="${c'$d'r#*:}";ca'$d'r="${c'$d'r%%:*}"'
d="d$d"
eval "c$d$cmds"
done
echo "$car , $cadr , $cddr"
bash独自のパラメータ展開という手はある (スコア:1)
http://d.hatena.ne.jp/dharry/20090211/1234290856 [hatena.ne.jp]
ただ、移植性が悪くなることと、あまり柔軟性がない点は覚悟が必要。それなら
と、全てを sed の拡張正規表現様にお任せする方が柔軟度が高いというものです (-人-)
# 南無正規表現如来
fjの教祖様
Re:bash独自のパラメータ展開という手はある (スコア:1)
アドバイスありがとうございます。
最初の例については、shibuyaさんのバージョンと同様に、str3にコロンが入ってくる形だとうまくいかなくなってしまいますね。
後者のsedを使うバージョンについては、s/([^:]*):([^:]*):(.*)/……/とすれば対応可能そうです。
本文に書いた、cutを使う方法と同様に、同じようなコマンドが連続するのが気になりますが、evalと組み合わせれば1回で済みそうですね。
……て、わかりづらいな。3日後にこれを解読できる自信がありません(汗
巧妙に潜伏したバグは心霊現象と区別が付かない。
Re:bash独自のパラメータ展開という手はある (スコア:1)
あ。vにシングルクォートを含めたら破綻した(- -;;
巧妙に潜伏したバグは心霊現象と区別が付かない。
Re:bash独自のパラメータ展開という手はある (スコア:1)
あぁっ、最後の条件を忘れてた。
そこを気にするぐらいなら、全体の制御の方を bash ではなく perl で書くことを選びます。
# 拡張正規表現の関係で perl を選ぶ。
というか、ちょうどその辺りが shell で済ませるか、もっと制御機能の付いたスクリプト言語に移行するかの境目ですね。
fjの教祖様
Re: (スコア:0)
split()
{
IFS=':' read a b c
echo "$a"
echo "$b"
echo "$c"
}
declare -a argv
argv=($(echo '123:456:789:ABC' | split))
echo "${argv[0]}"
echo "${argv[1]}"
echo "${argv[2]}"
Re:bash独自のパラメータ展開という手はある (スコア:1)
なるほど。readはほとんど使ったことがないので参考になります。
おや、直接 echo ... | read ... だとダメなのか。man確認しとこう。
巧妙に潜伏したバグは心霊現象と区別が付かない。
Re: (スコア:0)
パイプがあると別の世界(サブシェル)なので、変数はサブシェルのなかでのみ有効です。
どんなことがやりたいかわからないので、親シェルに値を返すためにあんなことをしていますが、
一番単純なパタンだと、これで十分です。
echo 1:2:3 | (IFS=':' read a b c; ここに処理を書く)
Re: (スコア:0)
ヒアドキュメントを使えば、read で読ませることもできますよ。
ググるとこんなのありますけど (スコア:0)
#!/bin/bash
TEXT='AAA;hoge;2345'
IFS=';'
set -- $TEXT
echo $1
echo $2
echo $3
Re: ググるとこんなのありますけど (スコア:1)
TEXT='AAA;hoge;2345;6789' の時に 「AAA」「hoge」「2345;6789」が欲しいんですよ。これだと3番目が「2345」になってしまうわけで。
shiftでずらしつつIFSで繋げて再構成すればいいか。
# IFSによる分割、Cのstrtok()的な動作 (文頭、文末の区切りは無視。連続する区切りはひとつの区切り扱い) だと
# 勘違いしてました。
巧妙に潜伏したバグは心霊現象と区別が付かない。
Re: ググるとこんなのありますけど (スコア:1)
この方向性ならこれで望みのものが得られました。アドバイスありがとうございます。
巧妙に潜伏したバグは心霊現象と区別が付かない。