为什么用"read-r-a line"拆分$PATH有效,而不使用"while read-



刚刚注意到一些奇怪的事情,我无法完全解释:

当我使用read -a分割我的$PATH变量时,一切都很好

IFS=: read -r -a lines <<< "$PATH"
for line in "${lines[@]}"; do echo "$line"; done

但当我尝试使用while ... read循环进行同样的操作时,只有第一行打印

while IFS=: read -r line; do echo "$line"; done <<< "$PATH"

可以实现此功能;从使用IFS=:切换到使用-d:,并在输入流的末尾附加一个:

while IFS= read -r -d: line; do echo "$line"; done <<< "$PATH:"

不同的是,IFS用于查找单词之间的边界,但read -r line只读取中的一个变量line,因此根本不查找多个单词。相反,-d告诉read的每次调用在哪个字符处停止;默认情况下,这是一个换行符,但您可以将其替换为任何其他单个字符。(如果找不到该字符,read将以非零状态退出;这就是为什么标准/惯用while read循环习惯用法在文件的最后一行没有正确地以换行符结束时会跳过它,以及为什么我们在此处使用$PATH:作为输入(。

另一方面,如果运行IFS=: read -r first second rest,它会将第一个PATH条目放入$first,第二个放入$second,并将行的其余部分放入$rest;而对于IFS: read -r line,就好像你只有一个项目,$rest

您的while循环处理1行,它不是循环。因此完整的路径被存储在字段CCD_ 22中
当您给定更多字段时,路径将被划分为这些字段(最后一个字段获得剩余字段(:

while IFS=: read -r line field2 field3 otherfields; do echo "$line"; done <<< "$PATH"

当你想避开阵列时,你可以使用

while read -r line; do echo "$line"; done <<< "${PATH//:/$'n'}"

它运行良好。

拆分成一个数组会得到一个开放数量的元素,您所期望的也是如此。

将数据拆分为单个变量也可以做同样的事情,但当它用完了要放入数据的变量名时,它会停止拆分,并将其余的放入最后一个。

试试这个:

$: IFS=: read -r a b c <<< "$PATH"
$: printf "[%s]n" "$a" "$b" "$c"

您将在$a中获得第一个PATH元素,在$b中获得第二个,而在$c中获得其余的ALL。

这样会更清楚吗?

c.f.本指南

为什么用read -r -a line拆分$PATH有效,而用while read -r line却无效?

因为read -r line读取整行,然后在读取整行之后读取,所以该行被吐在IFS上。因为您只为read提供了一个变量,所以所有的行都在这个变量中。你可以把第一个元素和其他元素上的线分开:

IFS=: read -r part1 rest_of_parts <<<"$line"

参见阅读1p阅读If there are fewer vars than fields,部分。请注意,当PATH包含换行符时,静态IFS=: read -r -a lines <<< "$PATH"将失败,如下所示:

$ export PATH=/usr/bin       # reset PATH to something short
$ cd /tmp/
$ mkdir temp$'n'dir         # create a directory with a newline in the name
$ ls -d tem*
'temp'$'n''dir'/
$ cd temp$'n'dir
$ printf "%sn" '#!/bin/bash' 'echo hello world' > script.sh
$ chmod +x ./script.sh       # add a script in that directory
$ export PATH="$PATH:$PWD"   # add that directory to path
$ ./script.sh                # yes. yes, it works
hello world
$ IFS=: read -r -a lines <<< "$PATH"
$ declare -p lines
declare -a lines=([0]="/usr/bin" [1]="/tmp/temp")
#                                              ^^^^ newline and 'dir' is missing
# That is because `read` reads _one line_ and one line only
# _after_ reading that one line that _one line_ is split on IFS
# so any more lines are ignored.

您可以使用对read-d的bash扩展,使read不读整行,而是读到一个字符(但我需要忽略read退出状态,不知道为什么(:

$ while IFS= read -r -d':' line || [[ -n "$line" ]]; do declare -p line; done < <(printf "%s" "$PATH")
declare -- line="/usr/bin"
declare -- line="/tmp/temp
dir"

请注意,<<<添加了一个尾随换行符,因此使用它将导致PATH的最后一个元素具有换行符——作为一种变通方法,在bash中,您可以使用进程替换< <(printf "%s" "$PATH")

如果使用bash,真正安全的解决方案只是使用mapfile/readarray:

$ mapfile -d: -t lines < <(printf "%s" "$PATH")
$ declare -p lines
declare -a lines=([0]="/usr/bin" [1]=$'/tmp/tempndir')

最新更新