![直接调用和 shell 变量中的 bufferin 的 find 输出不同](https://linux22.com/image/195482/%E7%9B%B4%E6%8E%A5%E8%B0%83%E7%94%A8%E5%92%8C%20shell%20%E5%8F%98%E9%87%8F%E4%B8%AD%E7%9A%84%20bufferin%20%E7%9A%84%20find%20%E8%BE%93%E5%87%BA%E4%B8%8D%E5%90%8C.png)
我在使用命令时遇到了一些奇怪的行为,find
但我找不到解释。
我有一个 .txt 文件,每行有 1 个文件名,我正在使用 find 命令在数据库中递归搜索该文件。当我使用这样的命令时:
for filename in `cat filelist.csv`; do
find /location*/time*/ -name *${filename}*txt
done
我得到每行 1 个输出的预期输出。但是,当我使用相同的命令但将输出设置为变量时(我最终需要这样做):
for filename in `cat filelist.csv`; do
out=`find /location*/time*/ -name *${filename}*txt`
echo ${out}
done
find 命令似乎在同一行上打印文件夹中的所有匹配文件。我有两个问题:
- 是什么导致了这种行为?
- 如何将
find
每个匹配文件(即使文件夹中有许多匹配文件)作为变量输出到新行?
干杯!
答案1
发生这种情况只是因为当 shell 扩展变量时,任何换行符都会被“折叠”并更改为空格。因此,如果您的out
变量包含换行符,${out}
请将所有这些换行符更改为空格。但"${out}"
保留换行符。
答案2
如果您filelist.csv
包含文件的完全匹配项,您可以使用类似find ... -print0 | grep -z -F -f filelist.csv | xargs -0r
...的内容,但您似乎想要匹配该文件中列出的部分文件名(文件名之前的任何字符和附加的“.txt”)。为此,最简单的方法是使用正则表达式。
您可以使用流程替代在 读取时将部分文件名转换filelist.csv
为适当的正则表达式。filelist.csv
grep
顺便说一句,除非您使用 sed 的-i
选项(不要针对此特定任务执行此操作),否则此转换不是永久性的,它不会影响原始 filelist.csv 文件,只会影响输入的文本流grep -f
。
或者,您可以将 的输出通过管道传输find . -name '*.txt'
到grep
。这样,grep 看到的输入已经过滤了以 结尾的文件名.txt
,因此sed
不需要修改正则表达式。
无论如何,尝试这样的事情:
首先,这个实验的一些设置:
$ cat filelist.csv
test
foo
$ touch test test.txt foo foo.txt footest footest.txt
$ ls -l
total 4
-rw-r--r-- 1 cas cas 10 Sep 8 04:01 filelist.csv
-rw-r--r-- 1 cas cas 0 Sep 8 04:01 foo
-rw-r--r-- 1 cas cas 0 Sep 8 04:01 footest
-rw-r--r-- 1 cas cas 0 Sep 8 04:01 footest.txt
-rw-r--r-- 1 cas cas 0 Sep 8 04:01 foo.txt
-rw-r--r-- 1 cas cas 0 Sep 8 04:01 test
-rw-r--r-- 1 cas cas 0 Sep 8 04:01 test.txt
然后使用 bash 内置函数mapfile
填充一个用out
find 和 grep 的输出调用的数组:
$ mapfile -d '' out < \
<(find . -type f -print0 |
grep -z -f <(sed -e 's/^\(.*\)/.*\1\.txt$/' filelist.csv)
或者:
$ mapfile -d '' out < \
<(find . -type f -name '*.txt' -print0 |
grep -z -f filelist.csv )
结果:
$ declare -p out
declare -a out=([0]="./foo.txt" [1]="./footest.txt" [2]="./test.txt")
$ ls -l "${out[@]}"
-rw-r--r-- 1 cas cas 0 Sep 8 04:01 ./footest.txt
-rw-r--r-- 1 cas cas 0 Sep 8 04:01 ./foo.txt
-rw-r--r-- 1 cas cas 0 Sep 8 04:01 ./test.txt
请注意数组out
仅包含foo.txt
、footest.txt
、 和test.txt
,但是不是 foo
或test
或footest
。
$out
顺便说一句,您可以使用以下内容迭代文件名:
for f in "${out[@]}"; do
echo "$f"
do-something-else-with "$f"
done
或者迭代数组的索引 (0, 1, 2) 而不是值 - 有时这更有用,例如,当您有两个或多个具有相同索引的数组并且您想以某种方式一起使用时,或者当您需要将索引用于其他目的时:
for i in "$!{out[@]}"; do
echo "${out[$i]}"
done
记住:
当您不希望 shell 对变量进行分词或展开 glob 或对变量中的 shell 元字符(如或 )进行操作时,请用双引号引用变量(即 type
"$var"
,而不仅仅是) 。这是$var
;
&
几乎总是。经验法则:如果您不确切知道为什么需要在任何特定情况下使用变量而不用双引号引起来,那么就用双引号引起来。没有引用$out
或者$filename
是您最初问题的直接原因。永远不要假设文件名中不会有烦人的字符,例如空格和换行符 - 这些对于 unix 中的文件名来说是完全有效的字符,因此您的脚本必须处理它们。事实上,仅有的不能出现在路径/文件名中的字符是 NUL。
始终使用 NUL 作为任意或未知文件名之间的分隔符。这是唯一可以使用的分隔符任何文件名。
有很多例外,但是:大多数时候,当您希望变量保存多个值时,应该使用数组,而不是空格分隔的字符串或类似的“伪造/模拟数组”方法。特别是当这些值是文件名或其他内容时,其中分隔符是其中一个值中的有效字符。
答案3
txt
要查找名称以 结尾并包含 中 行的任何内容的所有文件filelist.csv
,在zsh
shell 中您可以执行以下操作:
print -rC1 -- **/*(${(j[|])~${(fb)"$(<filelist.csv)"}}*})*txt(ND)
或者一次分解一步:
csv_contents=$(<filelist.csv)
non_empty_lines_of_csv=(${(f)csv_contents})
lines_with_wildcards_excaped=(${(b)non_empty_lines_of_csv})
ored_patterns=${(j[|])lines_with_wildcards_excaped}
filename_pattern="*($ored_patterns)*txt"
print -rC1 -- **/$~filename_pattern(ND)
如果filelist.csv
包含:
???
foo bar
baz
这最终会扩展一个递归全局,例如:
**/*(\?\?\?|foo bar|baz)*txt(ND)
至于你的问题:
for filename in `cat filelist.csv`; do out=`find /location*/time*/ -name *${filename}*txt` echo ${out} done
for var in `cmd`
不循环 的输出行cmd
,`cmd`
获取 的输出cmd
,删除尾随的换行符,然后对其执行 split+glob (仅在 zsh 中拆分),因此对 的字符进行拆分$IFS
(默认为空格、制表符和换行符),然后在生成的单词中展开通配符。因此,如果是cmd
outputa* b*
,它不会以var
being循环一次a* b*
,它会循环当前目录中以.开头的文件名a
,然后循环以.开头的文件名b
。- 在 中
-name *${filename}*.txt
,由于这些*
和${filename}
没有被引用,我们再次进行 shell 通配符操作。因此,如果${filename}
是abc
,*abc*txt
shell 将将 扩展至匹配文件列表。如果xabcytxt
当前目录中有一个文件被调用,则该文件将变为-name xabcytxt
.另外,即使您将其更正为-name "*${filename}*txt"
,如果$filename
是*
,也会-name '***txt'
返回find
所有以 结尾的文件名txt
,而不仅仅是那些也包含*
. - 在 中
echo ${out}
,再次$out
不加引号意味着 split+glob (zsh 中除外)。还echo
应该避免输出任意数据,因为在不同实现的各种极端情况下它不会执行您想要的操作。
也可以看看: