posix sh: 是否可以在不使用外部工具的情况下遍历字符串?

posix sh: is it possible to iterate through a string without using an external tool?

在 POSIX sh 中,我可以使用 while 循环遍历字符串中的每个字符并进行剪切;这是一个演示:

#!/bin/sh

lizards='geckos,anoles'

string_length="${#lizards}"

i=1
while [ "$i" -le "$string_length" ]; do
    char="$(echo "$lizards" | cut -c "$i")"
    echo "$char"
    i="$(( i + 1 ))"
done

输出为:

g
e
c
k
o
s
,
a
n
o
l
e
s

有什么方法可以不用调用外部命令吗?

通过使用 fold 而不是 cut:

,您可以轻松地将外部命令的数量从每个字符一个减少到整个字符串一个
#!/bin/sh

lizards='geckos,anoles'

echo "$lizards" | fold -w 1

我想不出一个纯粹用内置函数来做的好方法。但是我确实想到了一个丑陋的方式:

#!/bin/sh

lizards='geckos,anoles'

string_length="${#lizards}"

# Initialize j to length string_length-1 filled with ?'s and k to empty
i=1
j=
k=
while [ "$i" -lt "$string_length" ]; do
    j="?$j"
    i="$(( i + 1 ))"
done

i=0
# From here on, j contains string_length-i-1 ?'s and k contains i ?'s
while [ "$i" -lt "$string_length" ]; do
    char="${lizards#$k}"
    char="${char%$j}"
    echo "$char" # or printf '%c\n' "$char"
    j="${j#?}"
    k="?$k"
    i="$(( i + 1 ))"
done

要理解它,你必须先理解${var#pattern}${var%pattern}扩展,它们比较[=的开头(#)或结尾(%) 19=] 到模式(glob-like,不是正则表达式)并删除匹配的部分。 (这些匹配最短,##%% 匹配最长,但这在这里并不重要。)

因此,如果我想要删除前 2 个字符的 $var,我会使用 ${var#??}。如果我想删除最后 2 个字符,我使用 ${var%??}。所以我做了一个循环,使用可变数量的 ? 到 trim 每次都结束。基本上它是这样做的:

char=${lizards#}; char=${char%????????????}; echo $char
char=${lizards#?}; char=${char%???????????}; echo $char
char=${lizards#??}; char=${char%??????????}; echo $char
char=${lizards#???}; char=${char%?????????}; echo $char
char=${lizards#????}; char=${char%????????}; echo $char
char=${lizards#?????}; char=${char%???????}; echo $char
char=${lizards#??????}; char=${char%??????}; echo $char
char=${lizards#???????}; char=${char%?????}; echo $char
char=${lizards#????????}; char=${char%????}; echo $char
char=${lizards#?????????}; char=${char%???}; echo $char
char=${lizards#??????????}; char=${char%??}; echo $char
char=${lizards#???????????}; char=${char%?}; echo $char
char=${lizards#????????????}; char=${char%}; echo $char

我特别喜欢写行 j="${j#?}",它使用 ${var#pattern} 结构从变量中删除一个字符,该字符将用作下一个 ${var%pattern} 结构中的模式。

实际上,我想我会使用 fold

您可以使用printf获取第一个字符和参数扩展以将其删除:

#!/bin/sh

lizards='geckos,anoles'

while [ -n "$lizards" ]; do
    printf '%.1s\n' "$lizards"
    lizards="${lizards#?}"
done

几年前这里有一个类似于 POSIX 方法,它依赖于更多的变量,而不是 printf很多?)并且需要移除最小后缀模式${parameter%word})和移除最小前缀模式${parameter#word})参数扩展:

splitchar(){ unset r c
             q=""
             while [ "$q" ]; do  
                 s="$q" q="${q#?}" c="${s%$q}" r="$r$c
"
             done
             echo "${r%?}"; }

示例:

splitchar hello

输出:

h
e
l
l
o