如何在 bash shell 中将一个字符串拆分为多个字符串,至少用一个空格分隔?

Posted

技术标签:

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

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

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

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

【问题讨论】:

什么样的外壳? Bash、cmd.exe、powershell...? 您是否只需要循环(例如,为每个单词执行一个命令)?还是您需要存储一个单词列表以备后用? 【参考方案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='\s1,'
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" 分隔符 .

以上是关于如何在 bash shell 中将一个字符串拆分为多个字符串,至少用一个空格分隔?的主要内容,如果未能解决你的问题,请参考以下文章

在Bash中将字符串拆分为多个变量[重复]

如何在 Windows 中将 Git Bash 拆分为多个“视图”?

如何避免作为 sql 查询输出的一部分返回的字符串值被拆分为 bash/shell 脚本中数组中的不同字段

如何在shell中拆分字符串并获取最后一个字段

如何在 C++ 中将字符串拆分为数组

如何在 Bash 中将时间戳转换为日期?