如何在不折叠空格的情况下在 bash 脚本中拆分制表符分隔的字符串?

Posted

技术标签:

【中文标题】如何在不折叠空格的情况下在 bash 脚本中拆分制表符分隔的字符串?【英文标题】:How to split a tab-delimited string in bash script WITHOUT collapsing blanks? 【发布时间】:2013-11-12 05:52:06 【问题描述】:

我在$LINE 中有我的字符串,我希望$ITEMS 成为它的数组版本,拆分为单个标签保留空白。这是我现在的位置:

IFS=$'\n' ITEMS=($(echo "$LINE" | tr "\t" "\n"))

这里的问题是IFS 是一个或多个,因此它会吞噬换行符、制表符等。我根据此处发布的其他问题尝试了其他一些事情,但他们假设所有字段中总会有一个值,从不空白。而the one that seems to hold the key 远远超出了我的范围,并且对整个文件进行操作(我只是拆分一个字符串)。

我的偏好是纯 BASH 解决方案。

【问题讨论】:

【参考方案1】:

IFS 特殊字符:

Words of the form $'string' are treated specially.  The word expands to
string, with backslash-escaped characters replaced as specified by  the
ANSI  C  standard.  Backslash escape sequences, if present, are decoded
as follows:
       \a     alert (bell)
       \b     backspace
       \e
       \E     an escape character
       \f     form feed
       \n     new line
       \r     carriage return
       \t     horizontal tab
       \v     vertical tab
       \\     backslash
       \'     single quote
       \"     double quote
       \?     question mark
       \nnn   the eight-bit character whose value is  the  octal  value
              nnn (one to three digits)
       \xHH   the  eight-bit  character  whose value is the hexadecimal
              value HH (one or two hex digits)
       \uHHHH the Unicode (ISO/IEC 10646) character whose value is  the
              hexadecimal value HHHH (one to four hex digits)
       \UHHHHHHHH
              the  Unicode (ISO/IEC 10646) character whose value is the
              hexadecimal value HHHHHHHH (one to eight hex digits)
       \cx    a control-x character 

扩展的结果是单引号的,好像美元符号没有 一直在场。

以美元符号 ($"string") 开头的双引号字符串将导致 要根据当前语言环境翻译的字符串。如果 当前语言环境是 C 或 POSIX,忽略美元符号。如果 字符串被翻译和替换,替换是双引号。

【讨论】:

【参考方案2】:
line=$'zero\tone\ttwo'
IFS=$'\t' read -a arr <<< "$line"
declare -p

输出是

declare -a arr='([0]="zero" [1]="one" [2]="two")'

注意。这不处理line 中的换行符。

【讨论】:

【参考方案3】:

一个纯 bash 解决方案,只会在选项卡上拆分,并保留换行符和其他有趣的符号(如果有):

IFS=$'\t' read -r -a arr -d '' < <(printf '%s' "$line")

试试看:

$ line=$'zero\tone with\nnewlines\ttwo\t     three   \n\t\tfive\n'
$ IFS=$'\t' read -r -a arr -d '' < <(printf '%s' "$line")
$ declare -p arr
declare -a arr='([0]="zero" [1]="one with
newlines" [2]="two" [3]="     three   
" [4]="five
")'

如您所见,它完美无缺:它保留了所有内容(空格、换行符等),仅在制表符处拆分。

有一个缺点:它不处理“空字段”:观察line 中有两个连续的选项卡;我们希望在arr 中得到一个空字段,但事实并非如此。

还有另一个不太明显的缺点:read 的返回码是1,所以从技术上讲,对于 Bash,这个命令是失败的。这绝对不是问题,除非您使用的是 set -eset -E,但无论如何都不建议这样做(所以您不应该这样做)。

如果您能忍受这两个小缺点,这可能是理想的解决方案。

请注意,我们使用&lt; &lt;(printf '%s' "$line") 而不是&lt;&lt;&lt; "$line" 来提供read,因为后者会插入一个尾随换行符。

【讨论】:

【参考方案4】:

一种可能性:不要使用IFS 拆分,而是使用-d 选项将字符串中的read 以制表符结尾的“行”。但是,您需要确保您的字符串以制表符结尾,否则您将丢失最后一项。

items=()
while IFS='' read -r -d$'\t' x; do
   items+=( "$x" )
done <<< $'   foo   \t  bar\nbaz \t   foobar\t'

printf "===%s===\n" "$items[@]"

确保尾随标签不添加额外字段可以通过

来完成
if [[ $str != *$'\t' ]]; then str+=$'\t'; fi

如有必要。

【讨论】:

很有趣,我看到了-d并试图自己做点什么,但没有成功;我看到关键是使用循环(我尝试与-a 结合使用)。问题一:为什么要提前设置IFS='' 如果制表符分隔的字符串之一以空格开头或结尾是必要的,因为read 会在将x 的值设置为默认值IFS 之前将其剥离。跨度> 为了处理缺少的尾随换行符,您可以将 while 测试中的 read 语句替换为 IFS='' read -r -d$'\t' x || [[ $x ]],或者在 while 循环后添加 items+=( "$x" ) items+=("$x") 如果文件 没有 缺少最后的换行符,则循环后将附加一个空字符串,因此您需要像 (( $? )) &amp;&amp; items+=("$x") 这样的守卫。 (未经测试,并且存在棘手的极端情况,所以我不确定这是否 100% 正确。)【参考方案5】:

IFS 如果字符是空格,则只有一个或多个。非空白字符是单个分隔符。因此,一个简单的解决方案是,如果您确信某个非空白字符不在您的字符串中,则将制表符转换为该字符,然后对其进行拆分:

IFS=$'\2' read -ra ITEMS <<<"$LINE//$'\t'/$'\2'"

不幸的是,像“输入中没有\2 的实例”这样的假设从长远来看往往会失败,其中“从长远来看”转化为“在最坏的时间”。因此,您可能希望分两步完成:

IFS=$'\2' read -ra TEMP < <(tr $'\t\2' $'\2\t' <<<"$LINE")
ITEMS=("$TEMP[@]//$'\t'/$'\2'")

【讨论】:

一个非常优雅的解决方案。顺便说一下,作为参考,提到了将 IFS 空白字符序列视为分隔符的事情here。

以上是关于如何在不折叠空格的情况下在 bash 脚本中拆分制表符分隔的字符串?的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用全局变量的情况下在 bash 中返回一个数组?

如何在不使用 &nbsp 的情况下在行内元素之间添加空格 [重复]

如何在不使用边框间距和空行的情况下在带有边框的表格行之间添加空格

如何在不引入意外空格的情况下在多行上编写 f 字符串? [复制]

如何在不大幅增加其大小的情况下在 PowerShell 中保存文本文件?

如何在不运行Bash脚本的情况下语法检查?