2012-05-11 9 views
47

私はこのような変数を持っている:Bashの文字列中の各文字に対してforループを実行する方法は?

words="这是一条狗。" 

私は、文字のそれぞれのループのために一度に一つを作りたい、例えばcharacter="这"最初に、そしてなどcharacter="是"character="一"

私が知っている唯一の方法は、ファイルに出力し、別の行に各文字で、その後、while read lineを使用していますが、これは非常に非効率です。

  • どのように文字列内の各文字をforループで処理できますか?
+0

我々はOP *は*これは、彼らが何をしたいと考えている初心者の質問の多くを見ることを言及する価値があるかもしれませんする。非常に多くの場合、個々の文字を個別に処理する必要のない優れたソリューションが可能です。これは[XY問題](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)として知られています。適切な解決策は、実際にあなたが達成したいことを説明することです*あなたがそこに着くのを助けると思うステップを実行するだけではなく、あなたの質問。 – tripleee

答えて

29

LANG=en_US.UTF-8dashシェル上で、私は右の作業以下だ:したがって、出力はサンプルテキストのために編集したwhile read ... ; do ... ; done

でループすることができ

$ echo "你好嗎 新年好。全型句號" | sed -e 's/\(.\)/\1\n/g' 
你 
好 
嗎 

新 
年 
好 
。 
全 
型 
句 
號 

$ echo "Hello world" | sed -e 's/\(.\)/\1\n/g' 
H 
e 
l 
l 
o 

w 
o 
r 
l 
d 

は、翻訳する英語:

あなたは空白は無視されて気にしない場合
"你好嗎 新年好。全型句號" is zh_TW.UTF-8 encoding for: 
"你好嗎"  = How are you[ doing] 
" "   = a normal space character 
"新年好"  = Happy new year 
"。全型空格" = a double-byte-sized full-stop followed by text description 
+3

UTF-8でうまくいっています。私はそれを必要としませんでしたが、とにかく私のアップヴォートを手に入れました。 – Jordan

+0

+1 sedの結果の文字列に対してforループを使用することができます。 – Tyzoid

24

${#var}以降pos

例からvar

${var:pos:N}戻りNの文字の長さを返しますので、反復することが容易である

$ words="abc" 
$ echo ${words:0:1} 
a 
$ echo ${words:1:1} 
b 
$ echo ${words:2:1} 
c 

を。

別の方法:

$ grep -o . <<< "abc" 
a 
b 
c 

または

$ grep -o . <<< "abc" | while read letter; do echo "my letter is $letter" ; done 

my letter is a 
my letter is b 
my letter is c 
+1

空白はどうですか? –

+0

* *について*空白?空白文字は文字であり、これはすべての文字をループします。 (重要な空白を含む変数や文字列を二重引用符で囲むように注意する必要がありますが、より一般的には、あなたがしていることがわからない限り、常にすべてを引用してください)。(https://stackoverflow.com/questions/10067266/when -to-wrap-around-a-shell-variable)) – tripleee

12

私はASCII文字列でこれだけテストしてみたが、あなたのような何かを行うことができます:あなたが使用することができます

while test -n "$words"; do 
    c=${words:0:1}  # Get the first character 
    echo character is "'$c'" 
    words=${words:1} # trim the first character 
done 
148

をCスタイルforループ:

foo=string 
for ((i=0; i<${#foo}; i++)); do 
    echo "${foo:$i:1}" 
done 

${#foo}は、fooの長さに拡張されます。 ${foo:$i:1}は、長さ1の位置$iで始まる部分文字列に展開されます。sed

+0

なぜfor文が動作するには、for文の前後に2組のかっこが必要ですか? – tgun926

+0

これは 'bash'が必要とする構文です。 – chepner

+1

私はこれが古いことは知っていますが、2つの括弧は算術演算を可能にするために必要です。ここで参照してください>> http://tldp.org/LDP/abs/html/dblparens.html – Hannibal

0

別のアプローチは、:

for char in $(sed -E s/'(.)'/'\1 '/g <<<"$your_string"); do 
    # Handle $char here 
done 
3

foldを使用して文字配列に文字列を分割して、この配列を反復することも可能である:

for char in `echo "这是一条狗。" | fold -w1`; do 
    echo $char 
done 
15

私は驚いたことに、誰も明白なbashソリューションは、whilereadしか利用していないことに言及しました。

while read -n1 character; do 
    echo "$character" 
done < <(echo -n "$words") 

は終わりに余分な改行を避けるためにecho -nの使用に注意してください。 printfは別の良い選択肢であり、あなたの特定のニーズに適しています。空白を無視する場合は、"$words""${words// /}"に置き換えてください。

もう1つのオプションはfoldです。しかし、それは決してforループに送られるべきではないことに注意してください。次のようにむしろ、whileループを使用する:

while read char; do 
    echo "$char" 
done < <(fold -w1 <<<"$words") 

coreutilsのパッケージの)外部foldコマンドを使用する主な利点は、簡潔であろう。

fold -w1 <<<"$words" | xargs -I% -- echo % 

をあなたがしたいコマンドを使用して、上記の例で使用echoコマンドを交換したいと思う:あなたは、それは次のようなxargsなどの別のコマンド(のfindutilsパッケージの一部)への出力のフィードすることができますそれぞれのキャラクターに対して走るのが好きです。 xargsはデフォルトで空白を破棄します。 -d '\n'を使用して、その動作を無効にすることができます。


国際

私はアジアの一部の文字でfoldをテストし、それは、Unicodeをサポートしていません実現。だから、それはASCIIのニーズには問題ありませんが、それは誰にとってもうまくいかないでしょう。その場合、いくつかの選択肢があります。

awk 'BEGIN{FS=""} {for (i=1;i<=NF;i++) print $i}' 

それとも別の答えで述べたgrepコマンド::私はおそらくawkの配列でfold -w1を交換したい

FYI

grep -o . 


パフォーマンス、私は前述の3つのオプションをベンチマークしました。最初の2つは速く、ほぼ結びついていて、折り返しループはwhileループよりもわずかに速かった。無意識のうちにxargsが最も遅いです... 75倍遅くなりました。ここで

は(省略)テストコードです:ここで

words=$(python -c 'from string import ascii_letters as l; print(l * 100)') 

testrunner(){ 
    for test in test_while_loop test_fold_loop test_fold_xargs test_awk_loop test_grep_loop; do 
     echo "$test" 
     (time for ((i=1; i<$((${1:-100} + 1)); i++)); do "$test"; done >/dev/null) 2>&1 | sed '/^$/d' 
     echo 
    done 
} 

testrunner 100 

結果は以下のとおりです。

test_while_loop 
real 0m5.821s 
user 0m5.322s 
sys  0m0.526s 

test_fold_loop 
real 0m6.051s 
user 0m5.260s 
sys  0m0.822s 

test_fold_xargs 
real 7m13.444s 
user 0m24.531s 
sys  6m44.704s 

test_awk_loop 
real 0m6.507s 
user 0m5.858s 
sys  0m0.788s 

test_grep_loop 
real 0m6.179s 
user 0m5.409s 
sys  0m0.921s 
7

私が正しく、すべての空白文字を維持するだろう何の理想的な解決策はまだありませんし、高速であると考えてい十分なので、私は私の答えを投稿します。 ${foo:$i:1}を使用していますが、非常に遅く、大きな文字列で特に顕著です。これについては以下で説明します。どのように動作する

while IFS='' read -r -d '' -n 1 char; do 
     # do something with $char 
done < <(printf %s "$string") 

私の考えは、すべての文字を保持し、任意の文字列に対して正しく動作するようにいくつかの変更で、read -n1を伴う、によって提案された方法の拡張であります

  • IFS='' - 空の文字列に内部フィールドセパレータを再定義することはスペースとタブの剥離が防止されます。 readと同じ行にすると、他のシェルコマンドには影響しません。
  • -r - readが特別な行連結文字として行末に\を処理できないようにする "raw"を意味します。
  • -d '' - 区切り文字は改行文字をストリッピングreadを防止するように、空の文字列を渡します。実際には、nullバイトが区切り文字として使用されることを意味します。 -d ''-d $'\0'に等しい。
  • -n 1 - 一度に1文字ずつ読むことを意味します。
  • printf %s "$string" - をecho -nの代わりに使用すると、echo-n-eをオプションとして扱うので、より安全です。 "-e"を文字列として渡すと、echoは何も出力しません。
  • < <(...) - プロセス置換を使用してループに文字列を渡します。 here-stringsを代わりに使用すると(done <<< "$string")、末尾に改行文字が追加されます。また、パイプ(printf %s "$string" | while ...)に文字列を渡すと、ループはサブシェルで実行されます。つまり、すべての可変操作はループ内でローカルになります。

ここで、巨大な文字列でパフォーマンスをテストしましょう。 私はソースとして、次のファイルを使用:

#!/bin/bash 

# Saving contents of the file into a variable named `string'. 
# This is for test purposes only. In real code, you should use 
# `done < "filename"' construct if you wish to read from a file. 
# Using `string="$(cat makefiles.txt)"' would strip trailing newlines. 
IFS='' read -r -d '' string < makefiles.txt 

while IFS='' read -r -d '' -n 1 char; do 
     # remake the string by adding one character at a time 
     new_string+="$char" 
done < <(printf %s "$string") 

# confirm that new string is identical to the original 
diff -u makefiles.txt <(printf %s "$new_string") 

そして結果は次のとおりです:
https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt
次のスクリプトは、timeコマンドで呼ばれていました

$ time ./test.sh 

real 0m1.161s 
user 0m1.036s 
sys  0m0.116s 

私たちが見ることができるように、それはありますかなり速いです。
次に、私はパラメータ展開を使用するものでループを置き換え:正確な数は非常に異なるシステム上

$ time ./test.sh 

real 2m38.540s 
user 2m34.916s 
sys  0m3.576s 

かもしれませんが、:出力は、パフォーマンスの低下があり、正確にどのように悪い示し

for ((i=0 ; i<${#string}; i++)); do 
    new_string+="${string:$i:1}" 
done 

を全体像は似ているはずです。

0

もう一つの方法は次のとおりです。

Characters="TESTING" 
index=1 
while [ $index -le ${#Characters} ] 
do 
    echo ${Characters} | cut -c${index}-${index} 
    index=$(expr $index + 1) 
done 
0

私は私の解決策を共有:

read word 

for char in $(grep -o . <<<"$word") ; do 
    echo $char 
done 
関連する問題