私はこのような変数を持っています:
words="这是一条狗。"
character="这"
各文字に対して、一度に1つずつ、たとえば最初に、次にcharacter="是"
、character="一"
などのforループを作成したいと思います。
私が知っている唯一の方法は、各文字をファイル内の別々の行に出力してから使用することwhile read line
ですが、これは非常に非効率的なようです。
- 文字列内の各文字をforループで処理するにはどうすればよいですか?
Cスタイルのfor
ループを使用できます。
foo=string
for (( i=0; i<${#foo}; i++ )); do
echo "${foo:$i:1}"
done
${#foo}
の長さに拡張されfoo
ます。長さ1の${foo:$i:1}
位置から始まる部分文字列に展開されます。$i
sed
on dash
shell ofを使用する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
${#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")
( coreutilsパッケージの)外部fold
コマンドを使用する主な利点は簡潔さです。次のように、出力を( findutilsパッケージの一部)などの別のコマンドにフィードできます。xargs
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
すべての空白文字を正しく保持し、十分に高速な理想的なソリューションはまだないと思います。そのため、回答を投稿します。使用${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
です。「-e」を文字列として渡すと、何も出力されません。echo
-n
-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
を使用して文字列を文字配列に分割し、fold
この配列を反復処理することもできます。
for char in `echo "这是一条狗。" | fold -w1`; do
echo $char
done
@chepnerの答えのCスタイルのループはシェル関数update_terminal_cwd
にあり、grep -o .
解決策は賢いですが、を使用した解決策が見当たらないことに驚きましたseq
。これが私のものです:
read word
for i in $(seq 1 ${#word}); do
echo "${word:i-1:1}"
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は文字
POSIX準拠のシェルでASCII文字を繰り返すには、パラメーター拡張を使用して外部ツールを回避できます。
#!/bin/sh
str="Hello World!"
while [ ${#str} -gt 0 ]; do
next=${str#?}
echo "${str%$next}"
str=$next
done
また
str="Hello World!"
while [ -n "$str" ]; do
next=${str#?}
echo "${str%$next}"
str=$next
done
sedはUnicodeで動作します
IFS=$'\n'
for z in $(sed 's/./&\n/g' <(printf '你好嗎')); do
echo hello: "$z"
done
出力
hello: 你
hello: 好
hello: 嗎
空白が無視されることを気にしない場合の別のアプローチ:
for char in $(sed -E s/'(.)'/'\1 '/g <<<"$your_string"); do
# Handle $char here
done
別の方法は次のとおりです。
Characters="TESTING"
index=1
while [ $index -le ${#Characters} ]
do
echo ${Characters} | cut -c${index}-${index}
index=$(expr $index + 1)
done
私は自分の解決策を共有します:
read word
for char in $(grep -o . <<<"$word") ; do
echo $char
done
TEXT="hello world"
for i in {1..${#TEXT}}; do
echo ${TEXT[i]}
done
{1..N}
包括的範囲はどこですか
${#TEXT}
文字列内の文字の数です
${TEXT[i]}
-配列のアイテムのように文字列からcharを取得できます