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


224

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

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

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


1
什么样的外壳?Bash,cmd.exe,powershell ...?
Alexey Sviridov

您是否只需要循环(例如,对每个单词执行一个命令)?还是需要存储单词列表供以后使用?
DVK

Answers:


281

您是否尝试过仅将字符串变量传递给for循环?Bash会自动在空白处分割。

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

 

This
is
a
sentence.

1
@MobRule-唯一的缺点是您不能轻易捕获(至少我不记得一种方式)输出以进行进一步处理。请参阅下面的“ tr”解决方案,以了解将东西发送到STDOUT
DVK

4
您可以将其附加到变量:A=${A}${word})
卢卡斯·琼斯

1
设置$ text [将把单词放入$ 1,$ 2,$ 3 ...等]
Rajesh 2014年

32
实际上,此技巧不仅是错误的解决方案,而且由于外壳问题而极其危险touch NOPE; var='* a *'; for a in $var; do echo "[$a]"; done输出[NOPE] [a] [NOPE]而不是预期的输出[*] [a] [*](为了可读性,SPC替换了LF)。
蒂诺2015年

@mob如果我想基于某些特定的字符串分割字符串,该怎么办?示例“ .xlsx”分隔符。

296

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

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数组参考


26
令人遗憾的是,由于外壳问题,它还不是很完美:touch NOPE; var='* a *'; arr=($var); set | grep ^arr=输出arr=([0]="NOPE" [1]="a" [2]="NOPE")而不是预期的结果arr=([0]="*" [1]="a" [2]="*")
Tino

@Tino:如果您不想干扰地球,则只需将其关闭即可。然后,该解决方案也可以与通配符一起正常工作。我认为这是最好的方法。
亚历山德罗斯

3
@Alexandros我的方法是仅使用模式,这些模式默认情况下是安全的,并且可以在每个上下文中完美地工作。更改外壳程序球以获得安全解决方案的要求不仅是非常危险的途径,而且已经是阴暗的一面。所以我的建议是永远不要习惯在这里使用这种模式,因为迟早您会忘记一些细节,然后有人会利用您的错误。您可以在媒体上找到此类漏洞的证据。每一个 单。天。
蒂诺

86

只需使用内置的shell“设置”即可。例如,

设置$ text

在那之后,$ text中的单个单词将在$ 1,$ 2,$ 3等中。为了稳健起见,通常

设置-垃圾$ text
转移

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

text =“ This is a test”
设置-垃圾$ text
转移
言语 做
  回声“ [$ word]”
完成

此打印

[这个]
[是]
[一个]
[测试]

5
这是拆分var的好方法,这样可以直接访问各个部分。+1; 解决了我的问题
Cheekysoft 2011年

我本来建议使用,awkset要容易得多。我现在是一个set迷。谢谢@Idelic!
伊兹密尔·拉米雷斯

22
如果执行以下操作,请注意shell泛滥:touch NOPE; var='* a *'; set -- $var; for a; do echo "[$a]"; done输出[NOPE] [a] [NOPE]而不是预期的[*] [a] [*]仅当您确定101%的拆分字符串中没有SHELL元字符时才使用它!
蒂诺2015年

4
@Tino:这个问题不仅在这里,而且在所有情况下都适用,在这种情况下,您可以在set -f之前set -- $varset +f之后禁用全局性。
Idelic

3
@Idelic:好收获。使用set -f您的解决方案也是安全的。但这set +f是每个shell的默认设置,因此它是必不可少的细节,必须注意这一点,因为其他人可能不知道(我也一样)。
蒂诺2015年

81

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

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

arr将字符串分割后的部分放在哪里的数组),或者,如果输入中可能包含换行符,而您需要的不仅仅是第一行:

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

(请注意,中的空格-d ''不能保留),但这可能会给您一个意外的换行符<<<"$var"(因为这会在末尾隐式添加一个LF)。

例:

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

输出预期

[*]
[a]
[*]

因为此解决方案(与此处的所有先前解决方案相反)不容易发生意料之外的情况,而且常常无法控制外壳的膨胀。

另外,这还可以为您提供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    ]

请注意,IFSBASH 的处理本身就是一个主题,因此您的测试也要做一些有趣的话题:

  • 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 <<<"$var"),则请读取反斜杠转义符。这留给读者练习。


对于第二个问题:

为了测试我通常坚持使用的字符串中的内容case,因为它可以一次检查多个情况(注意:如果您需要使用穿插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%的所有需求。


2
这个答案应该得到更多upvotes,由于通配符问题凸显,它的全面性
布莱恩·阿格纽

@布莱恩谢谢。请注意,您可以使用set -fset -o noglob切换通配符,以使Shell元字符在这种情况下不再有害。但是我并不是一个真正的朋友,因为这会留下很多shell的功能,并且很容易在此设置之间来回切换。
蒂诺(Tino)2013年

2
美妙的答案,的确值得更多的赞扬。关于保护套的附带说明-您可以使用它来;&实现。不太确定出现在哪个版本的bash中。我是4.3的用户
谢尔盖Kolodyazhnyy

2
@Serg感谢您的注意,因为我还不知道!所以我了一下,它出现在Bash4中;&是像C中一样没有模式检查的强制失败。还有;;&继续执行进一步模式检查的功能。所以,;;就像是if ..; then ..; else if ..;;&if ..; then ..; fi; if ..,其中,;&就像是m=false; if ..; then ..; m=:; fi; if $m || ..; then ..-一个永不停止(向别人)学习;)
蒂诺

@Tino绝对正确-学习是一个连续的过程。实际上,;;&在您发表评论之前,我是不知道的 :D谢谢,也许这个壳就在您身边;)
Sergiy Kolodyazhnyy

43
$ 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

1
在BASH echo "X" |通常可以通过替代<<<"X",像这样:grep -s " " <<<"This contains SPC"。如果您做echo X | read var与对比的操作,则可以发现差异read var <<< X。只有后者将变量var导入当前shell,而在第一个变量中访问变量时,您必须像这样进行分组:echo X | { read var; handle "$var"; }
Tino,2015年

17

(A)要将句子拆分成单词(用空格分隔),只需使用以下命令即可使用默认的IFS:

array=( $string )


运行以下代码段的示例

#!/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的答案基本相同,但是通过这种方式,您可以存储数组以满足任何进一步的需求。如果只需要一个循环,则可以使用他的答案,该答案短了一行:)
-请参阅此问题,以获取基于定界符分割字符串的替代方法。


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

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

对于正则表达式提示(B),+ 1,但对于错误的解决方案(A),则为-1,因为这很容易导致shell globbing。;)
蒂诺2015年

6

仅使用bash检查空间:

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

1
echo $WORDS | xargs -n1 echo

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.