私はこのような変数を持っています:
words="这是一条狗。"
私は文字のそれぞれ1つずつ、例えば最初にforループを作りたいcharacter="这"
、そしてcharacter="是"
、character="一"
など
私が知っている唯一の方法は、各文字をファイル内の別々の行に出力してから使用することwhile read line
ですが、これは非常に非効率的なようです。
- 文字列内の各文字をforループで処理するにはどうすればよいですか?
私はこのような変数を持っています:
words="这是一条狗。"
私は文字のそれぞれ1つずつ、例えば最初にforループを作りたいcharacter="这"
、そしてcharacter="是"
、character="一"
など
私が知っている唯一の方法は、各文字をファイル内の別々の行に出力してから使用することwhile read line
ですが、これは非常に非効率的なようです。
回答:
sed
上dash
のシェルLANG=en_US.UTF-8
、私は右の作業下記を得ました:
$ 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
したがって、出力はループすることができます while read ... ; do ... ; done
サンプルテキスト用に編集されたものは英語に翻訳されます:
"你好嗎 新年好。全型句號" 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
Cスタイルのfor
ループを使用できます。
foo=string
for (( i=0; i<${#foo}; i++ )); do
echo "${foo:$i:1}"
done
${#foo}
の長さに拡張されfoo
ます。長さ1の${foo:$i:1}
位置から始まる部分文字列に展開され$i
ます。
bash
必要なことです。
for (( _expr_ ; _expr_ ; _expr_ )) ; do _command_ ; done
であり、$((expr))や((expr))と同じではないことを指摘したいと思います。3つのbash構造すべてで、exprは同じように扱われ、$((expr))もPOSIXです。
bash
は、算術コンテキストで評価される多くの式の1つにすぎません。
${#var}
の長さを返します var
${var:pos:N}
N文字pos
以降を返します
例:
$ 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
とbash
だけwhile
を利用した明白な解決策について誰も言及していないことに驚いていread
ます。
while read -n1 character; do
echo "$character"
done < <(echo -n "$words")
echo -n
最後に無関係な改行を避けるためにを使用していることに注意してください。printf
別の良いオプションであり、特定のニーズにより適している場合があります。空白を無視したい場合は、に置き換え"$words"
てください"${words// /}"
。
別のオプションはfold
です。ただし、forループにフィードしないように注意してください。むしろ、次のようにwhileループを使用します。
while read char; do
echo "$char"
done < <(fold -w1 <<<"$words")
fold
(coreutilsパッケージの)外部コマンドを使用する主な利点は簡潔さです。次のように、その出力をxargs
(findutilsパッケージの一部)などの別のコマンドにフィードできます。
fold -w1 <<<"$words" | xargs -I% -- echo %
echo
上記の例で使用されているコマンドを、各キャラクターに対して実行するコマンドに置き換える必要があります。xargs
デフォルトでは空白が破棄されることに注意してください。を使用-d '\n'
して、その動作を無効にすることができます。
fold
いくつかのアジア文字でテストしたところ、Unicodeがサポートされていないことがわかりました。したがって、ASCIIのニーズには問題ありませんが、すべての人に役立つわけではありません。その場合、いくつかの選択肢があります。
私はおそらくfold -w1
awk配列に置き換えます:
awk 'BEGIN{FS=""} {for (i=1;i<=NF;i++) print $i}'
または、grep
別の回答で言及されているコマンド:
grep -o .
参考までに、前述の3つのオプションのベンチマークを行いました。最初の2つは高速で、ほぼ同点であり、foldループは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
character
単純なwhile read
ソリューションでは空白の場合は空です。これは、異なるタイプの空白を互いに区別する必要がある場合に問題になる可能性があります。
read -n1
にread -N1
は、に変更する必要があることがわかりました。
すべての空白文字を正しく保持し、十分に高速な理想的なソリューションはまだないと思います。そのため、回答を投稿します。使用は${foo:$i:1}
機能しますが、非常に遅く、以下に示すように、大きな文字列で特に顕著です。
私の考えは、Sixによって提案された方法の拡張です。これにはread -n1
、すべての文字を保持し、任意の文字列に対して正しく機能するようにいくつかの変更が加えられています。
while IFS='' read -r -d '' -n 1 char; do
# do something with $char
done < <(printf %s "$string")
使い方:
IFS=''
-内部フィールドセパレータを空の文字列に再定義すると、スペースとタブが削除されなくなります。同じ行でそれを行うことは、read
他のシェルコマンドに影響を与えないことを意味します。-r
-「生」を意味し、行の終わりで特別な行連結文字としてread
扱わ\
れないようにします。-d ''
-空の文字列を区切り文字として渡すと、read
改行文字が削除されなくなります。実際には、ヌルバイトが区切り文字として使用されることを意味します。-d ''
に等しい-d $'\0'
。-n 1
-一度に1文字ずつ読み取られることを意味します。printf %s "$string"
-使用するprintf
代わりにしてecho -n
いるため、より安全でecho
扱い-n
や-e
オプションなど。「-e」を文字列として渡すと、echo
何も出力されません。< <(...)
-プロセス置換を使用して文字列をループに渡します。代わりにhere-strings(done <<< "$string"
)を使用すると、末尾に改行文字が追加されます。また、文字列をパイプ(printf %s "$string" | while ...
)に渡すと、ループがサブシェルで実行されます。つまり、すべての変数操作がループ内でローカルになります。それでは、巨大な文字列を使用してパフォーマンスをテストしてみましょう。次のファイルをソースとして使用しました:
https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt
次のスクリプトはtime
コマンドを介して呼び出されました。
#!/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")
そして結果は次のとおりです。
$ time ./test.sh
real 0m1.161s
user 0m1.036s
sys 0m0.116s
ご覧のとおり、非常に高速です。
次に、ループをパラメーター展開を使用するループに置き換えました。
for (( i=0 ; i<${#string}; i++ )); do
new_string+="${string:$i:1}"
done
出力は、パフォーマンスの低下がどれほど悪いかを正確に示しています。
$ time ./test.sh
real 2m38.540s
user 2m34.916s
sys 0m3.576s
正確な数はシステムによって大きく異なる場合がありますが、全体像は類似しているはずです。
私はこれを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
#!/bin/bash
word=$(echo 'Your Message' |fold -w 1)
for letter in ${word} ; do echo "${letter} is a letter"; done
出力は次のとおりです。
Yは文字oは文字uは文字rは文字Mは文字eは文字sは文字sは文字aは文字gは文字eは文字
空白が無視されることを気にしない場合の別のアプローチ:
for char in $(sed -E s/'(.)'/'\1 '/g <<<"$your_string"); do
# Handle $char here
done
TEXT="hello world"
for i in {1..${#TEXT}}; do
echo ${TEXT[i]}
done
{1..N}
包括的範囲はどこですか
${#TEXT}
文字列内の文字数です
${TEXT[i]}
-配列のアイテムのように文字列からcharを取得できます