我有一个 Bash shell 脚本中的字符串,我想将它分成一个字符的数组,不基于分隔符而是每个数组索引只有一个字符。我该如何做?最好不使用任何外部程序。让我重新表达一下,我的目标是可移植性,因此像 sed 这样的可能在任何 POSIX 兼容系统上都存在的东西都是可以接受的。
$ echo hello | awk NF=NF FS=
h e l l o
或者
$ echo hello | awk '$0=RT' RS=[[:alnum:]]
h
e
l
l
o
echo hello | original-awk NF=NF FS=
- user2350426
eval a=( $(echo "this is a test" | sed "s/\(.\)/'\1' /g") )
eval a=( $(echo "this is a test" | sed "s/\(.\)/'\1' /g") );v=0; echo Array: "${a[@]}"; while [[ $v -lt ${#a[@]} ]];do echo -ne "$v:\t" ; echo ${a[$v]}; let v=v+1;done
- Menachemstring=foo
unset chars
declare -a chars
while read -N 1
do
chars[${#chars[@]}]="$REPLY"
done <<<"$string"x
unset chars[$((${#chars[@]} - 1))]
unset chars[$((${#chars[@]} - 1))]
echo "Array: ${chars[@]}"
Array: f o o
echo "Array length: ${#chars[@]}"
Array length: 3
最后的x
是必需的,以处理在$string
之后附加换行符(如果没有包含一个)的事实。
如果您想使用NUL分隔的字符,可以尝试这个:
echo -n "$string" | while read -N 1
do
printf %s "$REPLY"
printf '\0'
done
如果您还需要支持带有换行符的字符串,可以这样做:
str2arr(){ local string="$1"; mapfile -d $'\0' Chars < <(for i in $(seq 0 $((${#string}-1))); do printf '%s\u0000' "${string:$i:1}"; done); printf '%s' "(${Chars[*]@Q})" ;}
string=$(printf '%b' "apa\nbepa")
declare -a MyString=$(str2arr "$string")
declare -p MyString
# prints declare -a MyString=([0]="a" [1]="p" [2]="a" [3]=$'\n' [4]="b" [5]="e" [6]="p" [7]="a")
while read -r -n1 c ; do arr+=("$c") ; done <<<"hejsan"
我知道这是一个“bash”问题,但请允许我向您展示zsh的完美解决方案,这是一种非常流行的shell:
string='this is a string'
string_array=(${(s::)string}) #Parameter expansion. And that's it!
print ${(t)string_array} -> type array
print $#string_array -> 16 items
AWK非常方便:
a='123'; echo $a | awk 'BEGIN{FS="";OFS=" "} {print $1,$2,$3}'
其中FS
和OFS
是读入和输出的分隔符
对于那些在搜索如何在fish中执行此操作的人:
我们可以使用内置的string
命令(自v2.3.0以来)进行字符串操作。
↪ string split '' abc
a
b
c
↪ for c in (string split '' abc)
echo char is $c
end
char is a
char is b
char is c
这是一个更复杂的例子,使用索引迭代字符串。
↪ set --local chars (string split '' abc)
for i in (seq (count $chars))
echo $i: $chars[$i]
end
1: a
2: b
3: c
declare -r some_string='abcdefghijklmnopqrstuvwxyz'
declare -a some_array
declare -i idx
for ((idx = 0; idx < ${#some_string}; ++idx)); do
some_array+=("${some_string:idx:1}")
done
for idx in "${!some_array[@]}"; do
echo "$((idx)): ${some_array[idx]}"
done
纯bash,无循环。
另一个解决方案,类似于/改编自 Léa Gris的解决方案,但使用read -a
代替readarray/mapfile
:
#!/usr/bin/env bash
str='azerty'
# Need extglob for the replacement pattern
shopt -s extglob
# Split string characters into array
# ${str//?()/$'\x1F'} replace each character "c" with "^_c".
# ^_ (Control-_, 0x1f) is Unit Separator (US), you can choose another
# character.
IFS=$'\x1F' read -ra array <<< "${str//?()/$'\x1F'}"
# now, array[0] contains an empty string and the rest of array (starting
# from index 1) contains the original string characters :
declare -p array
# Or, if you prefer to keep the array "clean", you can delete
# the first element and pack the array :
unset array[0]
array=("${array[@]}")
declare -p array
然而,我更喜欢更短的方式(对我来说更容易理解),即在分配数组之前删除初始的0x1f
:
#!/usr/bin/env bash
str='azerty'
shopt -s extglob
tmp="${str//?()/$'\x1F'}" # same as code above
tmp=${tmp#$'\x1F'} # remove initial 0x1f
IFS=$'\x1F' read -ra array <<< "$tmp" # assign array
declare -p array # verification
${string:1:1}
提取单个字符,但如果您需要支持POSIX sh,则不能假设您拥有它,也不能假设您根本具有数组。 - Charles Duffy