我有一个 Bash shell 脚本中的字符串,我想将它分成一个字符的数组,不基于分隔符而是每个数组索引只有一个字符。我该如何做?最好不使用任何外部程序。让我重新表达一下,我的目标是可移植性,因此像 sed 这样的可能在任何 POSIX 兼容系统上都存在的东西都是可以接受的。
尝试一下
echo "abcdefg" | fold -w1
编辑:添加了评论中提出的更优雅的解决方案。
echo "abcdefg" | grep -o .
echo "abcdefg" | grep -o .
是在 Bash 环境下将字符串拆分为单个字符的一种优雅方式。 - tripleeeecho "عمر" | fold -w1
,它会打印出空格和问号。然而,@tripleee的解决方法echo "عمر" | grep -o .
可以很好地解决问题。有趣的是,一些小程序无法通过https://dev59.com/O3RA5IYBdhLWcg3w4R_o :). 无论如何,还是感谢您优雅的回答。 - Omar Al-Ithawi你已经可以单独访问每个字母,无需进行数组转换:
$ foo="bar"
$ echo ${foo:0:1}
b
$ echo ${foo:1:1}
a
$ echo ${foo:2:1}
r
如果这还不够,你可以使用类似这样的方法:
$ bar=($(echo $foo|sed 's/\(.\)/\1 /g'))
$ echo ${bar[1]}
a
如果你甚至不能使用sed
或类似的工具,你可以使用上述第一种技术,结合使用while循环和原始字符串的长度(${#foo}
)来构建数组。thing=($(i=0; while [ $i -lt ${#foo} ] ; do echo ${foo:$i:1} ; i=$((i+1)) ; done))
echo "$foo"
。 - glenn jackmanfor i in $(seq ${#foo}); do echo "${foo:$i-1:1}"; done
- wjandreaseq
不符合 POSIX 标准。 - Charles Duffyseq
不符合 POSIX 标准。 - Charles Duffy0 .. ${#string}-1
的方式,我能想到只使用 bash 有两种方法:使用 =~
和使用 printf
。(还有第三个可能性是使用 eval
和 {..}
序列表达式,但这会使代码缺乏清晰性。)sed
)可能导致的失败问题。这些方法从 bash-3.0 开始就可用(发布于 2005 年)。=~
和正则表达式,将字符串转换为数组,只需一个表达式即可:string="wonkabars"
[[ "$string" =~ ${string//?/(.)} ]] # splits into array
printf "%s\n" "${BASH_REMATCH[@]:1}" # loop free: reuse fmtstr
declare -a arr=( "${BASH_REMATCH[@]:1}" ) # copy array for later
这个方法的原理是对字符串进行扩展,将每个单个字符替换为(.)
,然后使用分组来捕获每个单独字符到BASH_REMATCH[]
中生成的正则表达式匹配。由于特殊数组是只读的,因此索引0被设置为整个字符串,无法删除它,请注意在扩展数组时跳过索引0,如果需要可以使用:1
。
针对长度较大(>64个字符)的非平凡字符串进行的一些快速测试表明,这种方法比使用bash字符串和数组操作的方法要明显地快得多。=~
支持POSIX ERE,在该模式中.
除NUL外可匹配任何字符,即编译的正则表达式不包括REG_NEWLINE
。(在此方面,POSIX文本处理实用程序的行为默认情况下允许有所不同,并且通常确实如此。)printf
:string="wonkabars"
ii=0
while printf "%s%n" "${string:ii++:1}" xx; do
((xx)) && printf "\n" || break
done
这个循环递增索引ii
以逐个打印字符,并在没有剩余字符时退出。如果bash的printf
像C语言一样返回所打印字符数而不是错误状态,那么这将更加简单,但实际上所打印字符的数量被用%n
捕获到了变量xx
中。(至少从bash-2.05b开始有效。)
使用bash-3.1和printf -v var
,您有更多的灵活性,可以避免因执行其他操作(例如创建数组)而导致越界问题:
declare -a arr
ii=0
while printf -v cc "%s%n" "${string:(ii++):1}" xx; do
((xx)) && arr+=("$cc") || break
done
i=0
while [ $i -lt ${#x} ]; do y[$i]=${x:$i:1}; i=$((i+1));done
for (( i=0 ; i < ${#x} ; i++ )); do y[i]=${x:i:1}; done
看起来更符合惯用的写法。 - user2350426我发现以下方法最佳:
array=( `echo string | grep -o . ` )
注意反引号:
如果你执行以下命令:echo ${array[@]}
,
你会得到:s t r i n g
或者: echo ${array[2]}
,
你会得到:r
$ read -a ARRAY <<< $(echo "abcdefg" | sed 's/./& /g')
并测试
$ echo ${ARRAY[0]}
a
$ echo ${ARRAY[1]}
b
< p > 解释: read -a
命令将标准输入读取为一个数组,并将其分配给变量 ARRAY,将空格视为每个数组项的分隔符。
通过将字符串回显到 sed 中进行评估,只需在每个字符之间添加所需的空格即可。
我们使用Here String (<<<) 来提供 read 命令的标准输入。
无需循环的纯Bash解决方案:
#!/usr/bin/env bash
str='The quick brown fox jumps over a lazy dog.'
# Need extglob for the replacement pattern
shopt -s extglob
# Split string characters into array (skip first record)
# Character 037 is the octal representation of ASCII Record Separator
# so it can capture all other characters in the string, including spaces.
IFS= mapfile -s1 -t -d $'\37' array <<<"${str//?()/$'\37'}"
# Strip out captured trailing newline of here-string in last record
array[-1]="${array[-1]%?}"
# Debug print array
declare -p array
string=hello123
for i in $(seq 0 ${#string})
do array[$i]=${string:$i:1}
done
echo "zero element of array is [${array[0]}]"
echo "entire array is [${array[@]}]"
[h]
。整个数组是[h e l l o 1 2 3 ]
。又有一个问题:'将字符串拆分为字符数组',但没有详细说明接收数组的状态,也没有详细说明特殊字符(如空格和控制字符)。
我的假设是,如果我想将字符串拆分为字符数组,我希望接收数组仅包含该字符串,不留任何之前运行的内容,并保留任何特殊字符。
例如,提出的解决方案类似于
for (( i=0 ; i < ${#x} ; i++ )); do y[i]=${x:i:1}; done
目标数组中有剩余项。
$ y=(1 2 3 4 5 6 7 8)
$ x=abc
$ for (( i=0 ; i < ${#x} ; i++ )); do y[i]=${x:i:1}; done
$ printf '%s ' "${y[@]}"
a b c 4 5 6 7 8
除了每次想要拆分问题时都写一长串代码,为什么不将所有这些隐藏到一个函数中,我们可以将其保存在包源文件中,并使用类似以下的API:
s2a "Long string" ArrayName
$ s2a()
> { [ "$2" ] && typeset -n __=$2 && unset $2;
> [ "$1" ] && __+=("${1:0:1}") && s2a "${1:1}"
> }
$ a=(1 2 3 4 5 6 7 8 9 0) ; printf '%s ' "${a[@]}"
1 2 3 4 5 6 7 8 9 0
$ s2a "Split It" a ; printf '%s ' "${a[@]}"
S p l i t I t
patsub_replacement
和=~
操作符进行正则表达式。与@mr.spuratic的帖子/答案基本相同。str='There can be only one, the Highlander.'
regexp="${str//?/(&)}"
[[ "$str" =~ $regexp ]] &&
printf '%s\n' "${BASH_REMATCH[@]:1}"
declare -p BASH_REMATCH
unset -v 'BASH_REMATCH[0]'
printf
或echo
来打印数组BASH_REMATCH
的值已经不再需要。
"$regexp"
的值:declare -p regexp
输出
declare -- regexp="(T)(h)(e)(r)(e)( )(c)(a)(n)( )(b)(e)( )(o)(n)(l)(y)( )(o)(n)(e)(,)( )(t)(h)(e)( )(H)(i)(g)(h)(l)(a)(n)(d)(e)(r)(.)"
或者
echo "$regexp"
shopt
,尽管手册说默认情况下它是开启的/启用的。if ! shopt -q patsub_replacement; then
shopt -s patsub_replacement
fi
bash
版本!如果您不确定正在使用哪个版本的 bash
。if ! ((BASH_VERSINFO[0] >= 5 && BASH_VERSINFO[1] >= 2)); then
printf 'No dice! bash version 5.2+ is required!\n' >&2
exit 1
fi
regexp
变量中排除,将其更改为regexp="${str//?/(&)}"
regexp="${str//[! ]/(&)}"
declare -- regexp="(T)(h)(e)(r)(e) (c)(a)(n) (b)(e) (o)(n)(l)(y) (o)(n)(e) (t)(h)(e) (H)(i)(g)(h)(l)(a)(n)(d)(e)(r)(.)"
${string:1:1}
提取单个字符,但如果您需要支持POSIX sh,则不能假设您拥有它,也不能假设您根本具有数组。 - Charles Duffy