如何在 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 -f
和 set +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 ''
中的空格;不能省略),但这可能会给您带来来自<<<"$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 <<<"$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 -f
或set -o noglob
来切换通配符,这样shell 元字符在这种情况下就不再有害了。但我并不是真正的朋友,因为这留下了 shell 的强大功能/在此设置来回切换时很容易出错。
精彩的答案,确实值得更多的支持。关于案例失败的旁注 - 您可以使用 ;&
来实现这一点。不太确定出现在哪个版本的 bash 中。我是 4.3 用户
@Serg 感谢您的注意,因为我还不知道!所以我查了一下,它出现在Bash4。 ;&
是没有像 C 中那样的模式检查的强制失败。还有;;&
只是继续进行进一步的模式检查。所以;;
就像if ..; then ..; else if ..
和;;&
就像if ..; then ..; fi; if ..
,其中;&
就像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" |
通常可以替换为 <<<"X"
,如下所示:grep -s " " <<<"This contains SPC"
。如果您执行echo X | read var
与read var <<< 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 中将一个字符串拆分为多个字符串,至少用一个空格分隔?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Windows 中将 Git Bash 拆分为多个“视图”?