【问题标题】:Bash: Split string into character arrayBash:将字符串拆分为字符数组
【发布时间】:2011-11-26 14:15:51
【问题描述】:

我在 Bash shell 脚本中有一个字符串,我想将它拆分为一个字符数组,而不是基于分隔符,而是每个数组索引只有一个字符。我怎样才能做到这一点? 理想情况下,它不会使用任何外部程序。让我重新表述一下。我的目标是可移植性,所以像 sed 这样的东西可能在任何 POSIX 兼容的系统上都可以。

【问题讨论】:

  • 如果您的平台是 POSIX,则 bash 不是给定的。
  • @tripleee 也不是数组。
  • 当然。我试图理解这个问题。也许 OP 意味着将 Bash 定位在其他 POSIX 系统上?
  • 最初的目的是创建一个可以在不了解用户平台的情况下在线共享的shell脚本。所以我希望尽可能多地兼容 OS X、Ubuntu 等。我不需要 100% 兼容 Unix 的外来变体。

标签: string bash


【解决方案1】:

您无需数组转换即可单独访问每个字母:

$ 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 或类似的东西,您可以使用上面的第一种技术结合使用原始字符串长度 (${#foo}) 的 while 循环来构建数组。

警告:如果字符串包含空格,下面的代码将不起作用。我认为Vaughn Cato's answer 有更好的机会在特殊字符下生存。

thing=($(i=0; while [ $i -lt ${#foo} ] ; do echo ${foo:$i:1} ; i=$((i+1)) ; done))

【讨论】:

  • 别忘了引用:echo "$foo"
  • 你建议的循环:for i in $(seq ${#foo}); do echo "${foo:$i-1:1}"; done
【解决方案2】:

如果您的字符串存储在变量 x 中,则会生成一个包含单个字符的数组 y:

i=0
while [ $i -lt ${#x} ]; do y[$i]=${x:$i:1};  i=$((i+1));done

【讨论】:

  • This:for (( i=0 ; i < ${#x} ; i++ )); do y[i]=${x:i:1}; done 看起来更适合 bash。
【解决方案3】:

试试

echo "abcdefg" | fold -w1

编辑:添加了 cmets 中建议的更优雅的解决方案。

echo "abcdefg" | grep -o .

【讨论】:

  • 尽管使用了外部命令,但因为简洁而+1。
  • unstableme.blogspot.fi/2009/07/… 有一个相当优雅的建议echo "abcdefg" | grep -o .
  • @xdazz 它不适用于 Unicode。试试这个echo "عمر" | fold -w1 它会打印空格和问号。但是@tripleee 的解决方案echo "عمر" | grep -o . 确实可以正常工作。有趣的是小程序如何不通过stackoverflow.com/q/796986/161278 :)。无论如何感谢您的优雅回答。
  • @OmarIthawi 谢谢,已将其添加到答案中。
  • @OmarIthawi:这两种变体都适用于我,在 Mac OS X 和 Linux CentOS 6.5 上,所以它似乎不像“折叠解决方案不适用于 unicode”那么简单。跨度>
【解决方案4】:

如果你想把它存储在一个数组中,你可以这样做:

string=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

【讨论】:

    【解决方案5】:

    AWK相当方便:

    a='123'; echo $a | awk 'BEGIN{FS="";OFS=" "} {print $1,$2,$3}'
    

    其中FSOFS 是读入和打印的分隔符

    【讨论】:

      【解决方案6】:

      如果文本可以包含空格:

      eval a=( $(echo "this is a test" | sed "s/\(.\)/'\1' /g") )
      

      【讨论】:

      • 使用来自stackoverflow.com/a/7581114/394952 的信息来显示存储在数组“a”中的字符。像这样: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
      【解决方案7】:
      $ echo hello | awk NF=NF FS=
      h e l l o
      

      或者

      $ echo hello | awk '$0=RT' RS=[[:alnum:]]
      h
      e
      l
      l
      o
      

      【讨论】:

      • 警告:使用空 FS 的结果会随着 awk 实现而改变。它是明确的avoided by POSIX:«1.- 如果 FS 是空字符串,则行为未指定。»。更具体:失败:echo hello | original-awk NF=NF FS=
      【解决方案8】:

      作为使用 for/while 循环迭代 0 .. ${#string}-1 的替代方法,我还可以考虑使用其他两种方法仅使用 bash:使用 =~ 和使用 @987654324 @。 (还有第三种可能使用eval{..} 序列表达式,但这不够清晰。)

      在 bash 中启用正确的环境和 NLS 后,这些将如希望的那样与非 ASCII 一起工作,如果担心的话,可以消除使用旧系统工具(如 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
      

      其工作方式是执行string 的扩展,将(.) 替换为每个单个字符,然后将此生成的正则表达式与分组相匹配,以将每个单独的字符捕获到BASH_REMATCH[] 中。索引 0 设置为整个字符串,因为该特殊数组是只读的,您无法将其删除,如果需要,请注意当数组扩展以跳过索引 0 时的 :1。 对重要字符串(>64 个字符)的一些快速测试表明,此方法大大比使用 bash 字符串和数组操作的方法快。

      以上内容适用于包含换行符的字符串,=~ 默认支持POSIX ERE where . matches anything except NUL,即编译正则表达式时没有REG_NEWLINE。 (POSIX文本处理utilities的行为在这方面默认是允许不同的,通常是这样。)

      第二个选项,使用printf

      string="wonkabars"
      ii=0
      while printf "%s%n" "${string:ii++:1}" xx; do 
        ((xx)) && printf "\n" || break
      done 
      

      此循环增加索引ii 以一次打印一个字符,并在没有剩余字符时中断。如果 bash printf 返回打印的字符数(如在 C 中)而不是错误状态,这将更加简单,而不是使用 %nxx 中捕获打印的字符数。 (这至少可以追溯到 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
      

      【讨论】:

        【解决方案9】:
        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 ]

        【讨论】:

        • 这些子字符串提取操作优于涉及通过子进程管道传输字符串的等效解决方案。
        【解决方案10】:

        最简单、最完整、最优雅的解决方案:

        $ read -a ARRAY <<< $(echo "abcdefg" | sed 's/./& /g')  
        

        和测试

        $ echo ${ARRAY[0]}
          a
        
        $ echo ${ARRAY[1]}
          b
        

        解释read -a 将标准输入作为数组读取,并将其分配给变量 ARRAY,将空格作为每个数组项的分隔符。

        将字符串回显到 sed 的评估只是在每个字符之间添加所需的空格。

        我们使用Here String (

        【讨论】:

          【解决方案11】:

          对于那些登陆这里并在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
          

          【讨论】:

            【解决方案12】:

            如果您还需要对带有换行符的字符串的支持,您可以这样做:

            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")
            

            作为对 Alexandro de Oliveira 的回应,我认为以下内容更优雅或至少更直观:

            while read -r -n1 c ; do arr+=("$c") ; done <<<"hejsan"
            

            【讨论】:

              【解决方案13】:

              zsh 解决方案:将标量string 变量放入arr,这将是一个数组:

              arr=(${(ps::)string})
              

              【讨论】:

                【解决方案14】:

                我发现以下方法效果最好:

                array=( `echo string | grep -o . ` )
                

                (注意反引号)

                如果你这样做:echo ${array[@]}, 你得到:s t r i n g

                或:echo ${array[2]}, 你得到:r

                【讨论】:

                  【解决方案15】:

                  另一个关于:),所述问题只是说“将字符串拆分为字符数组”,并没有多说接收数组的状态,也不要说太多关于特殊字符和控制字符的内容。

                  我的假设是,如果我想将一个字符串拆分为一个字符数组,我希望接收数组只包含该字符串,并且没有以前运行的剩余,但保留任何特殊字符。

                  例如建议的解决方案系列,如

                  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 
                  

                  【讨论】:

                    【解决方案16】:
                    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
                    

                    【讨论】:

                      【解决方案17】:

                      没有循环的纯 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 
                      

                      【讨论】:

                      • 很好,你可以: - 创建一个函数并 - 发布一些示例!
                      猜你喜欢
                      • 1970-01-01
                      • 2021-10-09
                      • 1970-01-01
                      • 2012-02-22
                      • 2016-04-01
                      相关资源
                      最近更新 更多