我正在尝试验证我读到的以下内容这里:
重要的是要记住,shell 执行我们按顺序描述的所有类型的扩展。这意味着单词扩展在路径名扩展之前执行。因此,如果您循环遍历扩展路径的结果,则不会对这些结果执行分词。
我编写了一个非常简单的脚本,用于验证上面关于单词和路径名扩展的操作顺序的引用:
$ cat test.sh
for path in /home/john/Downloads/*.xlsx
do
echo "${path}"
done
结果
$ ./test.sh
/home/john/Downloads/CCIE-Collaboration-v3-Learning-Matrix.xlsx
/home/john/Downloads/x y.xlsx
据我所知,单词扩展发生在路径扩展之前,他们使用了一个与此非常相似的脚本来演示它。但我实在不明白这个例子是如何演示的?据我所知,“path”是一个变量,在该变量标识路径之后,该变量将是$path。那么地球上哪里会有单词扩展呢,因为我看不到任何可以考虑扩展的单词。
为了澄清,如果我们运行第一次迭代,$path 是空的,直到它检查“/home/john/Downloads/*.xlsx”,此时它被定义为“/home/john/Downloads/CCIE-Collaboration” -v3-Learning-Matrix.xlsx”,因为这是第一个条目。此时 $path 已被定义。它没有机会应用单词扩展(例如,他们是否说在实际路径名定义第一个结果之前将单词扩展应用于“null”)?
答案1
当作者写道单词扩展,他们在页面上的其他任何地方都没有使用这一术语(但据我所知,这个术语确实完全正确,请参阅 POSIX 规范这里),他们似乎指的是拆分操作,他们称之为“分词”。这是 shell 将根据变量值$IFS
(通常设置为空格、制表符和换行符)进行拆分的过程。例如,考虑这个脚本(取自这里,这是一个很好的参考,我强烈建议您阅读它):
#!/bin/sh -
printf "%d args:" "$#"
[ "$#" -eq 0 ] || printf " <%s>" "$@"
echo
该脚本将简单地打印出其参数,显示它们是如何拆分为单词的。尝试使用不同的输入运行:
$ foo.sh apple orange "passion fruit"
3 args: <apple> <orange> <passion fruit>
在这里,字符串apple orange "passion fruit"
被分成三个“单词” apple
,orange
和passion fruit
。这就是分词。请注意如何防止引号passion fruit
被拆分。
现在,正如文章所述,顺序很重要。如果您阅读了“扩展”部分bash手册, 你会找到:
展开的顺序是:大括号展开;波形符扩展、参数和变量扩展、算术扩展和命令替换(以从左到右的方式完成);分词;和文件名扩展。
这里相关的一点是,我上面展示的分词发生在文件名(路径)扩展之前。你的误解在这里:
为了澄清,如果我们运行第一次迭代,$path 是空的,直到它检查“/home/john/Downloads/*.xlsx”,此时它被定义为“/home/john/Downloads/CCIE-Collaboration” -v3-Learning-Matrix.xlsx”,因为这是第一个条目。此时 $path 已被定义。
不,$path
此时已定义,它不会扩展到/home/john/Downloads/CCIE-Collaboration-v3-Learning-Matrix.xlsx
循环内部,而是在循环开始之前。我们可以使用与您所拥有的稍有不同的脚本来分解这两者,即分词和路径名扩展:
#!/bin/bash
words="x y.xlsx *.xlsx"
for word in $words; do
echo "word is '$word'"
done
在包含以下内容的目录中尝试该脚本:
$ ls -1
CCIE-Collaboration-v3-Learning-Matrix.xlsx
'x y.xlsx'
输出是:
$ foo.sh
word is 'x'
word is 'y.xlsx'
word is 'CCIE-Collaboration-v3-Learning-Matrix.xlsx'
word is 'x y.xlsx'
那么发生了什么?
变量
$words
是分裂转化为文字的价值$IFS
。结果是 3 个单词:z
、y.xlsx
和*.xlsx
。接下来,这三个单词中的每一个都进行路径扩展。这使得
x
和y.xlsx
保持不变,因为它们不能扩展到路径,而是*.xlsx
变成x y.xlsx
和CCIE-Collaboration-v3-Learning-Matrix.xlsx
。
作者试图表达的观点是,由于在 shell 扩展*.xlsx
为 x y.xlsx
和时已经发生了分词CCIE-Collaboration-v3-Learning-Matrix.xlsx
,因此不会发生进一步的分词,并且x y.xlsx
尽管包含空格,但仍被视为单个单词。
请注意,这个答案的第一个版本是错误的,因为我很困惑代币化与分词。
答案2
我相信他们试图解释的是,在 shell 扩展/home/john/Downloads/*.xlsx
为/home/john/Downloads/CCIE-Collaboration-v3-Learning-Matrix.xlsx
and
/home/john/Downloads/x y.xlsx
(“路径名扩展”)之后,它并没有尝试再次将其拆分为/home/john/Downloads/CCIE-Collaboration-v3-Learning-Matrix.xlsx
,
/home/john/Downloads/x
and y.xlsx
(“分词”-不是“单词扩展”)。所有这些都是在for
循环开始为变量赋值之前进行的。因此,如果要对路径名扩展的结果执行分词,则循环仅迭代两个值,而不是我们会看到的三个值。这些都与循环体内发生的事情无关。
另一方面,如果有for i in $(echo a b c)
,那么 shell 会对命令替换的结果进行分词,并且您会看到三个循环迭代,而不是从 得到的循环迭代for i in "$(echo a b c)"
。
答案3
这句话“这意味着单词扩展是在路径名扩展之前执行的。”引用中的内容毫无意义,因为来源从未定义“单词扩展”应该是什么。在下一句话中,他们提到了分词,这在页面前面已经描述过,可能就是他们的意思。所以,忽略“单词扩展”这个短语,在这种情况下它是错误的,简单明了。
引用的下一句话更有意义:
因此,如果您循环遍历扩展路径的结果,则不会对这些结果执行分词。
考虑以下情况,我们创建两个文件oneword
并two words
在一个空目录中,然后循环./*
:
touch 'oneword' 'two words'
i=0
for f in ./*; do
printf "%d: %s\n" "$i" "$f"
i=$(( i+1 ))
done
输出如下,显示循环迭代了 、 两项./oneword
,./two words
这两项是路径名扩展的直接结果,而后一个文件名没有进一步拆分。
0: ./oneword
1: ./two words
如果发生分词后路径名扩展,第二个文件名将进一步拆分为./two
words
,并且循环将总共运行三次。 (假设默认IFS
。)
上面的 Q 继续
据我所知,单词扩展发生在路径扩展之前,他们使用了一个与此非常相似的脚本来演示它。
但正如上面所说,这是没有意义的。您引用的来源没有定义“单词扩展”对他们意味着什么,并且不清楚您对这个短语的含义。这不是你的错:你被一个无法保持其术语一致的来源所困惑。
现在,从技术上来说,POSIX规范对所有波形符扩展、参数扩展、命令替换、算术扩展、字段拆分(通常称为单词拆分)、路径名/文件名扩展和引号删除使用短语“单词扩展”。但是您的消息来源使用该短语的上下文与该定义没有意义。单词扩展不能发生在路径扩展“之前”,因为单词扩展包括路径扩展。
那么,地球这个词的扩展会出现在哪里呢,因为我看不到任何可以考虑扩展的词语。
再次强调,取决于你的意思。 POSIX 意义上的“分词”几乎涵盖了所有内容,因此在 Q 中的脚本中,有“单词扩展”(路径名扩展)到/home/john/Downloads/*.xlsx
文件名,以及“单词扩展”(参数扩展)到"${path}"
当前文件名中。变量的值path
。 (我上面的脚本也有算术展开的情况。)