如何对Bash中的string中的每个字符执行for循环?
我有这样的variables:
words="这是一条狗。"  我想对每个字符做一个for循环,例如,第一个character="这" ,然后是character="是" , character="一"等。 
 我知道的唯一方法是输出每个字符来分隔文件中的行,然后while read line使用,但这似乎效率很低。 
- 我如何通过for循环来处理string中的每个字符?
 在LANG=en_US.UTF-8 dashshell上,我得到了如下工作: 
 $ 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循环while read ... ; do ... ; done 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的长度。  ${foo:$i:1}展开为从长度为1的位置$i开始的子string。 
  ${#var}返回${#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 
 我很惊讶没有人提到明显的bash解决scheme只使用和read 。 
 while read -n1 character; do echo "$character" done < <(echo -n "$words") 
 注意使用echo -n来避免最后的无用换行符。  printf是另一个不错的select,可能更适合您的特定需求。 如果你想忽略空格,用"${words// /}"replace"$words" "${words// /}" 。 
 另一种select是fold 。 但是请注意,它不应该被送入for循环。 相反,使用while循环如下: 
 while read char; do echo "$char" done < <(fold -w1 <<<"$words") 
 使用外部fold命令( coreutils包)的主要好处是简洁。 您可以将其输出提供给另一个命令,如xargs ( findutils包的一部分),如下所示: 
 fold -w1 <<<"$words" | xargs -I% -- echo % 
 你需要用上面例子中使用的echo命令replace你想要对每个字符运行的命令。 请注意, xargs默认会丢弃空格。 您可以使用-d '\n'来禁用该行为。 
国际化
 我只testing了一些亚洲字符的fold ,并意识到它没有Unicode支持。 所以虽然对于ASCII需求来说没问题,但它不适用于所有人。 在这种情况下,有一些替代scheme。 
 我可能会用awk数组replacefold -w1 : 
 awk 'BEGIN{FS=""} {for (i=1;i<=NF;i++) print $i}' 
 或者在另一个答案中提到的grep命令: 
 grep -o . 
性能
 仅供参考,我以上述三个选项为基准。 前两个是快速,几乎搭售,折叠循环比while循环稍快。 不出所料xargs是最慢的… 75倍慢。 
这是(缩写)testing代码:
 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 
我只用asciistringtesting过,但是你可以这样做:
 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 
 我相信仍然没有理想的解决scheme能够正确保留所有的空白字符,并且速度足够快,所以我会发布我的答案。 使用${foo:$i:1}可以工作,但速度很慢,对于大string尤为明显,如下所示。 
 我的想法是由Six提出的一种方法的扩展,其中涉及到read -n1 ,其中一些更改保留所有字符并正确地为任何string工作: 
 while IFS='' read -r -d '' -n 1 char; do # do something with $char done < <(printf %s "$string") 
怎么运行的:
-   IFS=''– 将内部字段分隔符重新定义为空string可防止空白和制表符被剥离。 与read同一行意味着它不会影响其他shell命令。
-   -r– 意思是“原始的”,它防止read作为特殊行连接字符在行尾处理。
-   -d ''– 将空string作为分隔符传递,防止read换行符。 实际上意味着空字节被用作分隔符。-d ''等于-d $'\0'。
-   -n 1– 表示一次读取一个字符。
-   printf %s "$string"– 使用printf而不是echo -n更安全,因为echo将-n和-e当作选项。 如果将“-e”作为string传递,则echo将不会打印任何内容。
-   < <(...)– 使用进程replace将string传递给循环。 如果你在这里使用string(done <<< "$string"),结尾会追加一个额外的换行符。 另外,通过pipe道传递string(printf %s "$string" | while ...)会使循环运行在一个子shell中,这意味着所有的variables操作在循环中都是局部的。
 现在,让我们用一个巨大的string来testing性能。 我使用以下文件作为来源: 
  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 
确切的数字可能在不同的系统上,但整体情况应该是相似的。
 也可以使用fold将string拆分为一个字符数组,然后遍历这个数组: 
 for char in `echo "这是一条狗。" | fold -w1`; do echo $char done 
另一种方法,如果你不关心被忽略的空白:
 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