为什么循环find的输出是一种不好的做法
1. 概述
有时,我们需要处理文件列表。我们可以使用find 命令生成文件列表。然后,由于在 Bash 中遍历 列表很容易,我们可能会想直接遍历find的输出。
在本教程中,我们将讨论为什么遍历find的输出是一种不好的做法。首先,我们将了解这样做会产生哪些问题。然后,我们将讨论我们可以使用哪些替代方案来代替循环查找find的输出。
2. 为什么这是一个坏习惯
当我们编写脚本时,我们应该遵循良好的做法。这使我们能够构建一个健壮的脚本,可以毫无问题地处理任何有效输入。此外,当我们遵循良好实践时,使用新功能扩展和改进我们的脚本会更容易。
在 Linux 中,我们可以在文件名中使用广泛的字符集。这包括字母数字字符、空格、单引号、双引号、换行符等。
我们需要使用项目定界符来遍历项目列表。但是,find打印由换行符分隔的每个文件,换行符也是文件名中的有效字符。
**因此,遍历find的输出被认为是不好的做法,因为我们无法选择适用于所有有效文件名的列表定界符。**此外,当我们遍历列表时,我们应该考虑带有空格的文件,因为有时 Bash 将空格视为分隔符。
我们可以测试这个问题,看看当我们有一个名称中包含空格的文件和另一个包含换行符的文件时会发生什么。让我们在一个空文件夹中创建三个文件:
$ touch file1
$ touch 'file 2'
$ touch 'file
3'
$ ls
file\n3 file\ 2 file1
*我们注意到,*“file\n3”文件在“file”和数字 3 之间有一个新行。
现在,让我们尝试对每个文件运行ls 命令,使用for循环遍历find的输出:
$ for F in `find -type f`; do
ls "$F"
done
/bin/ls: cannot access './file': No such file or directory
/bin/ls: cannot access '3': No such file or directory
/bin/ls: cannot access './file': No such file or directory
/bin/ls: cannot access '2': No such file or directory
./file1
如我们所见,我们无法处理带空格的文件或带换行符的文件。
让我们尝试使用while循环和管道find的输出到read命令来改进迭代:
$ find -type f | while read F; do
ls "$F"
done
/bin/ls: cannot access './file': No such file or directory
/bin/ls: cannot access '3': No such file or directory
./file\ 2
./file1
这次,我们可以处理带有空格的文件。但是,我们仍然无法处理带有换行符的文件。
3. 循环文件名的替代方案
到目前为止,我们已经了解到循环遍历find的输出是一种不好的做法,因为我们无法处理所有可能的文件名。现在,让我们看看我们必须使用哪些替代方法来正确处理使用find获得的文件列表。
3.1. 使用*-exec* 参数
我们可以用来编写更健壮的脚本的一种方法是让迭代自行查找。我们可以使用-exec参数告诉find*对每个文件执行一个程序。*
使用这种方法,我们可以编写一个接受文件名的脚本,而不是使用 Bash for循环或while循环。然后,我们可以在我们的新脚本中使用find的*-exec*参数,find将对每个文件运行我们的脚本。
当我们使用-exec参数时,我们必须提供脚本名称,然后是在文件名位置使用{}的参数,最后是一个; 终止 -exec 参数。**
在上一节的示例中,我们的循环体运行ls “$F”。所以现在,让我们将之前的循环体移动到一个名为work_on_file.sh的新脚本中,将文件名作为第一个参数:
#!/bin/bash
ls "$1"
如我们所见,这是一个微不足道的例子。但是,我们可以在work_on_file.sh文件中编写更复杂的 Bash 脚本。 现在,我们可以使用新脚本运行find 。让我们使用*-exec参数运行find*来处理每个文件:
$ find -type f -exec ./work_on_file.sh {} \;
./file\n3
./work_on_file.sh
./file\ 2
./file1
如我们所见,我们的脚本列出了所有没有错误的文件。此外,查找包含我们的work_on_file.sh脚本,因为它也在同一文件夹中。
在这种情况下,我们的脚本只对每个文件运行ls。因此,我们可以通过运行find -type f -exec ls {} ;来使用ls而不是我们的脚本。
3.2. 使用xargs 命令
我们还有另一种方法来处理find的输出。我们可以将find与-print0参数一起使用,并将其通过管道传递给带有-0*参数的/xargs 命令。*
当我们使用*-print0参数时,find用空字符分隔每个文件名。我们不能在 Bash 中使用空字符,但是,我们可以将xargs与-0参数一起使用。使用-0*参数,xargs用空字符分隔每个项目。我们可以处理任意文件名列表,因为空字符在文件名中是无效的。
** xargs接受命令作为参数,然后它使用xargs的输入作为命令的参数来执行该命令。我们可以使用-n参数限制每个命令的参数数量。在这种情况下,我们必须使用 -n 1,因为我们的work_on_file.sh脚本只接受一个参数。
让我们将find的输出通过管道传递给xargs,以便它运行work_on_file.sh脚本:
$ find -type f -print0 | xargs -0 -n 1 ./work_on_file.sh
./file\n3
./work_on_file.sh
./file\ 2
./file1
如我们所见,我们的脚本列出了所有文件而没有错误。
我们的脚本只对每个文件运行ls,因此我们也可以通过运行find -type f -print0 | xargs -0 ls,将xargs与*ls一起使用。
**我们还可以修改脚本以接受任意数量的参数。*这样,我们就不需要使用-n 1*参数。然而,我们可以通过只接受一个参数来保持我们的脚本简单,从而避免使用循环。
如果我们想要接受多个参数,我们可以通过遍历*“ $@ ”*变量来实现。让我们修改我们的脚本,看看它是如何工作的:
#!/bin/bash
for F in "$@"; do
ls "$F"
done
现在,我们可以使用不带*-n参数的xargs*:
$ find -type f -print0 | xargs -0 ./work_on_file.sh
./file\n3
./work_on_file.sh
./file\ 2
./file1
正如我们所见,即使我们遍历列表,脚本也能正常工作。**我们使用双引号括起来的特殊变量 $@ **