【问题标题】:How to split one string into multiple strings separated by at least one space in bash shell?如何在 bash shell 中将一个字符串拆分为多个字符串,至少用一个空格分隔?
【发布时间】:2010-11-30 23:43:58
【问题描述】:

我有一个包含许多单词的字符串,每两个单词之间至少有一个空格。如何将字符串拆分为单个单词以便循环遍历它们?

字符串作为参数传递。例如。 ${2} == "cat cat file"。如何循环遍历它?

另外,如何检查字符串是否包含空格?

【问题讨论】:

  • 什么样的外壳? Bash、cmd.exe、powershell...?
  • 您是否只需要循环(例如,为每个单词执行一个命令)?还是您需要存储一个单词列表以备后用?

标签: bash shell string split


【解决方案1】:

$echo foo bar baz | sed 's/ /\n/g'

foo
bar
baz

【讨论】:

    【解决方案2】:

    对此的另一种看法(使用 Perl):

    $ echo foo bar baz | perl -nE 'say for split /\s/'
    foo
    bar
    baz
    

    【讨论】:

      【解决方案3】:

      对于我的用例,最好的选择是:

      grep -oP '\w+' file
      

      基本上这是一个匹配连续的非空白字符的正则表达式。这意味着任何类型和任何数量的空格都不会匹配。 -o 参数将每个匹配的单词输出到不同的行。

      【讨论】:

        【解决方案4】:

        只需使用内置的 shell "set"。例如,

        set $text
        

        之后,$text 中的单个单词将在 $1、$2、$3 等中。为了健壮性,通常会这样做

        set -- junk $text
        shift
        

        处理 $text 为空或以破折号开头的情况。例如:

        text="This is          a              test"
        set -- junk $text
        shift
        for word; do
          echo "[$word]"
        done
        

        打印出来

        [This]
        [is]
        [a]
        [test]
        

        【讨论】:

        • 这是拆分 var 以便可以直接访问各个部分的绝佳方式。 +1;解决了我的问题
        • 我本来建议使用awk,但set 更容易。我现在是set 粉丝。谢谢@Idelic!
        • 如果你这样做,请注意 shell globbing:touch NOPE; var='* a *'; set -- $var; for a; do echo "[$a]"; done 输出 [NOPE] [a] [NOPE] 而不是预期的 [*] [a] [*]仅当您 101% 确定拆分后的字符串中没有 SHELL 元字符时才使用它!
        • @Tino:这个问题无处不在,不仅在这里,但在这种情况下,您可以在 set -- $var 之前 set -fset +f 之后禁用通配符。
        • @Idelic:很好。使用set -f,您的解决方案也很安全。但是set +f是每个shell的默认值,所以这是一个必不可少的细节,必须注意,因为其他人可能不知道(我也是)。
        【解决方案5】:

        可能在 BASH 3 及更高版本中最简单、最安全的方法是:

        var="string    to  split"
        read -ra arr <<<"$var"
        

        (其中arr 是获取字符串拆分部分的数组)或者,如果输入中可能有换行符并且您想要的不仅仅是第一行:

        var="string    to  split"
        read -ra arr -d '' <<<"$var"
        

        (请注意-d '' 中的空格;不能省略),但这可能会给您带来来自&lt;&lt;&lt;"$var" 的意外换行符(因为这会在末尾隐式添加一个LF)。

        例子:

        touch NOPE
        var="* a  *"
        read -ra arr <<<"$var"
        for a in "${arr[@]}"; do echo "[$a]"; done
        

        输出预期

        [*]
        [a]
        [*]
        

        因为此解决方案(与此处所有以前的解决方案相比)不易出现意外且通常无法控制的 shell globbing。

        这也为您提供了您可能想要的 IFS 的全部功能:

        例子:

        IFS=: read -ra arr < <(grep "^$USER:" /etc/passwd)
        for a in "${arr[@]}"; do echo "[$a]"; done
        

        输出类似:

        [tino]
        [x]
        [1000]
        [1000]
        [Valentin Hilbig]
        [/home/tino]
        [/bin/bash]
        

        如您所见,这样也可以保留空格:

        IFS=: read -ra arr <<<' split  :   this    '
        for a in "${arr[@]}"; do echo "[$a]"; done
        

        输出

        [ split  ]
        [   this    ]
        

        请注意,在 BASH 中对 IFS 的处理本身就是一个主题,因此请进行测试;一些有趣的话题:

        • unset IFS:忽略 SPC、TAB、NL 的运行以及在线开始和结束
        • IFS='': 没有字段分离,只读取所有内容
        • IFS=' ':SPC 运行(和仅 SPC)

        一些最后的例子:

        var=$'\n\nthis is\n\n\na test\n\n'
        IFS=$'\n' read -ra arr -d '' <<<"$var"
        i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done
        

        输出

        1 [this is]
        2 [a test]
        

        同时

        unset IFS
        var=$'\n\nthis is\n\n\na test\n\n'
        read -ra arr -d '' <<<"$var"
        i=0; for a in "${arr[@]}"; do let i++; echo "$i [$a]"; done
        

        输出

        1 [this]
        2 [is]
        3 [a]
        4 [test]
        

        顺便说一句:

        • 如果你不习惯$'ANSI-ESCAPED-STRING'就习惯了;这是一个节省时间。

        • 如果您不包含-r(如read -a arr &lt;&lt;&lt;"$var"),则 read 会反斜杠转义。这留给读者作为练习。


        第二个问题:

        要测试字符串中的某些内容,我通常坚持使用case,因为这可以一次检查多个案例(注意:案例只执行第一个匹配项,如果您需要通过使用多个case 语句),并且这种需求经常出现(双关语):

        case "$var" in
        '')                empty_var;;                # variable is empty
        *' '*)             have_space "$var";;        # have SPC
        *[[:space:]]*)     have_whitespace "$var";;   # have whitespaces like TAB
        *[^-+.,A-Za-z0-9]*) have_nonalnum "$var";;    # non-alphanum-chars found
        *[-+.,]*)          have_punctuation "$var";;  # some punctuation chars found
        *)                 default_case "$var";;      # if all above does not match
        esac
        

        所以你可以像这样设置返回值来检查 SPC:

        case "$var" in (*' '*) true;; (*) false;; esac
        

        为什么是case?因为它通常比正则表达式序列更具可读性,并且由于 Shell 元字符,它可以很好地处理 99% 的所有需求。

        【讨论】:

        • 这个答案值得更多的支持,因为它突出了全局问题,而且它的全面性
        • @brian 谢谢。请注意,您可以使用set -fset -o noglob 来切换通配符,这样shell 元字符在这种情况下就不再有害了。但我并不是真正的朋友,因为这留下了 shell 的强大功能/在此设置来回切换时很容易出错。
        • 精彩的答案,确实值得更多的支持。关于案例失败的旁注 - 您可以使用 ;&amp; 来实现这一点。不太确定出现在哪个版本的 bash 中。我是 4.3 用户
        • @Serg 感谢您的注意,因为我还不知道!所以我查了一下,它出现在Bash4;&amp; 是没有像 C 中那样的模式检查的强制失败。还有;;&amp; 只是继续进行进一步的模式检查。所以;; 就像if ..; then ..; else if ..;;&amp; 就像if ..; then ..; fi; if ..,其中;&amp; 就像m=false; if ..; then ..; m=:; fi; if $m || ..; then ..——一个人永远不会停止学习(向他人学习);)
        • 对于不太熟悉使用 bash 数组变量的人来说,如果您回显希望看到数组内容的数组变量,您只会看到第一个元素,因此这可能无法正常工作。使用 echo "${ARRAY[*]}" 查看内容。
        【解决方案6】:
        echo $WORDS | xargs -n1 echo
        

        这会输出每个单词,之后您可以根据需要处理该列表。

        【讨论】:

          【解决方案7】:

          我喜欢转换为数组,以便能够访问单个元素:

          sentence="this is a story"
          stringarray=($sentence)
          

          现在您可以直接访问单个元素(以 0 开头):

          echo ${stringarray[0]}
          

          或转换回字符串以便循环:

          for i in "${stringarray[@]}"
          do
            :
            # do whatever on $i
          done
          

          当然,直接循环遍历字符串之前已经回答过了,但是那个回答的缺点是不能跟踪单个元素以供以后使用:

          for i in $sentence
          do
            :
            # do whatever on $i
          done
          

          另见Bash Array Reference

          【讨论】:

          • 很遗憾不是很完美,因为 shell-globbing:touch NOPE; var='* a *'; arr=($var); set | grep ^arr= 输出 arr=([0]="NOPE" [1]="a" [2]="NOPE") 而不是预期的 arr=([0]="*" [1]="a" [2]="*")
          • @Tino:如果您不希望 globbing 干扰,那么只需将其关闭即可。然后,该解决方案也可以与通配符一起正常工作。我认为这是最好的方法。
          • @Alexandros 我的方法是只使用模式,这些模式在默认情况下是安全的,并且可以在任何情况下完美地工作。改变 shell-globbing 以获得安全解决方案的要求不仅仅是一条非常危险的道路,它已经是黑暗的一面。所以我的建议是永远不要习惯在这里使用这样的模式,因为迟早你会忘记一些细节,然后有人会利用你的错误。您可以在媒体上找到此类漏洞的证据。每一个。单身的。天。
          【解决方案8】:

          (A) 要将句子拆分成单词(空格分隔),您可以简单地使用默认的 IFS,通过使用

          array=( $string )
          


          示例运行以下 sn-p

          #!/bin/bash
          
          sentence="this is the \"sentence\"   'you' want to split"
          words=( $sentence )
          
          len="${#words[@]}"
          echo "words counted: $len"
          
          printf "%s\n" "${words[@]}" ## print array
          

          会输出

          words counted: 8
          this
          is
          the
          "sentence"
          'you'
          want
          to
          split
          

          如您所见,您也可以使用单引号或双引号,没有任何问题

          注意事项:
          -- 这与mob 的回答基本相同,但是通过这种方式,您可以存储数组以备不时之需。如果你只需要一个循环,你可以使用他的答案,它短了一行:)
          -- 请参考this question 了解基于分隔符拆分字符串的替代方法。


          (B) 要检查字符串中的字符,您还可以使用正则表达式匹配。
          检查您可以使用的空格字符是否存在的示例:

          regex='\s{1,}'
          if [[ "$sentence" =~ $regex ]]
              then
                  echo "Space here!";
          fi
          

          【讨论】:

          • 对于正则表达式提示 (B) a +1,但对于错误解决方案 (A) -1,因为这很容易导致 shell globbing。 ;)
          【解决方案9】:

          仅使用 bash 检查空格:

          [[ "$str" = "${str% *}" ]] && echo "no spaces" || echo "has spaces"
          

          【讨论】:

            【解决方案10】:
            $ echo "This is   a sentence." | tr -s " " "\012"
            This
            is
            a
            sentence.
            

            要检查空格,请使用 grep:

            $ echo "This is   a sentence." | grep " " > /dev/null
            $ echo $?
            0
            $ echo "Thisisasentence." | grep " " > /dev/null     
            $ echo $?
            1
            

            【讨论】:

            • 在 BASH 中,echo "X" | 通常可以替换为 &lt;&lt;&lt;"X",如下所示:grep -s " " &lt;&lt;&lt;"This contains SPC"。如果您执行echo X | read varread var &lt;&lt;&lt; X 相比,您可以发现差异。只有后者将变量 var 导入当前 shell,而要在第一个变体中访问它,您必须像这样进行分组:echo X | { read var; handle "$var"; }
            【解决方案11】:

            您是否尝试将字符串变量传递给for 循环?一方面,Bash 会自动分割空格。

            sentence="This is   a sentence."
            for word in $sentence
            do
                echo $word
            done
            

             

            This
            is
            a
            sentence.
            

            【讨论】:

            • @MobRule - 唯一的缺点是您无法轻松捕获(至少我不记得有一种方法)输出以进行进一步处理。有关将内容发送到 STDOUT 的内容,请参见下面的“tr”解决方案
            • 你可以将它附加到一个变量中:A=${A}${word}).
            • set $text [这会将单词放入$1,$2,$3...等]
            • 实际上这个技巧不仅是一个错误的解决方案,而且由于shell globbing,它也是非常危险touch NOPE; var='* a *'; for a in $var; do echo "[$a]"; done 输出 [NOPE] [a] [NOPE] 而不是预期的 [*] [a] [*](LF 被 SPC 替换以提高可读性)。
            • @mob 如果我想根据某个特定的字符串拆分字符串,我该怎么办?示例 ".xlsx" 分隔符 .
            猜你喜欢
            • 1970-01-01
            • 2012-10-03
            • 2012-01-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-05-18
            相关资源
            最近更新 更多