我有一个像这样的变量:
words="这是一条狗。"
我想打一个for循环的每个字符,一次一个,例如,第一character="这"
,然后character="是"
,character="一"
等
我知道的唯一方法是将每个字符输出到文件中的单独行,然后使用while read line
,但这似乎效率很低。
- 如何通过for循环处理字符串中的每个字符?
我有一个像这样的变量:
words="这是一条狗。"
我想打一个for循环的每个字符,一次一个,例如,第一character="这"
,然后character="是"
,character="一"
等
我知道的唯一方法是将每个字符输出到文件中的单独行,然后使用while read line
,但这似乎效率很低。
Answers:
随着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
。${foo:$i:1}
从$i
长度1的位置开始扩展到子字符串。
bash
要求。
for (( _expr_ ; _expr_ ; _expr_ )) ; do _command_ ; done
与$(((expr))或((expr))都不相同。在所有三个bash构造中,将expr视为相同,并且$((expr))也是POSIX。
bash
在算术上下文中求值的众多表达式之一。
${#var}
返回的长度 var
${var:pos:N}
从此pos
开始返回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
仅使用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 .
仅供参考,我以上述三个选项为基准。前两个速度很快,几乎是平手,而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
意味着它不会影响其他Shell命令。-r
-均值“原始”,这防止read
从处理\
在该行作为特殊线路连接字符的结束。-d ''
-传递空字符串作为分隔符可防止read
剥离换行符。实际上意味着将空字节用作分隔符。-d ''
等于-d $'\0'
。-n 1
-表示一次将读取一个字符。printf %s "$string"
-使用printf
而不是echo -n
更安全,因为echo
将-n
和-e
视为选项。如果将“ -e”作为字符串传递,echo
则不会打印任何内容。< <(...)
-使用进程替换将字符串传递给循环。如果使用here-strings代替(done <<< "$string"
),则会在末尾附加一个额外的换行符。另外,通过管道(printf %s "$string" | while ...
)传递字符串将使循环在子shell中运行,这意味着所有变量操作都在循环内是局部的。现在,让我们用一个巨大的字符串测试性能。我使用以下文件作为源:
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
确切的数字可能在不同的系统上非常不同,但是总体情况应该相似。
#!/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
另一种方法是:
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