当我使用find
查看目录中的所有 pdf 文件时/home
,我看到的是access denied
。为了消除它们,我尝试了:
find /home -iname "*.pdf" | grep -v "access denied"
但结果是一样的。我该如何去掉这些线条?
答案1
您尝试的方法不起作用,因为access denied
输出是错误并且通过 STDERR 发送,而不是通过管道发送到的 STDOUT grep
。
您可以通过仅重定向 STDERR 来避免看到这些错误
find /home -iname "*.pdf" 2>/dev/null
或者David Foerster 评论我们可以更简洁地关闭标准错误
find /home -iname "*.pdf" 2>&-
不过,我怀疑你实际上只想搜索你的家,而不是其他用户的家,所以也许你真的想要
find ~ -iname "*.pdf"
如果出现错误,则可能是您的本地配置中存在一些错误的所有权,您应该对此进行调查。
答案2
被拒绝的访问可能被打印在stderr
而不是stdout
。
尝试这个:
find /home -iname "*.pdf" 2>&1 | grep -v "access denied"
将2>&1
输出从 重定向stderr
到stdout
,以便grep -v
可以完成其工作。(默认情况下,|
只有管道stdout
而不是stderr
。)
答案3
你可能想说的是“权限被拒绝”——find
在 Ubuntu 中向您显示何时由于文件权限而无法访问某些内容 - 而不是“拒绝访问”。
一个完全通用的命令可以正确地做到这一点(并且作为奖励,是便携的到其他*尼克斯es,只要错误信息相同)是:
(find 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
(通常您想向 传递一些参数find
。这些参数在第一次重定向之前进行3>&1
。)
但是,通常你可以使用更简单的方法。例如,你可以使用流程替代. 详情如下。
最常用的方法及其局限性
两种典型的方法是扔掉标准错误(例如Zanna 的回答)或将 stderr 重定向到标准输出并过滤标准输出(如Android Dev 的回答)虽然这些方法具有编写简单的优点并且通常是合理的选择,但它们并不理想。
丢弃发送到 stderr 的所有内容—例如将其重定向到空设备用2>/dev/null
或用关闭它2>&-
——可能会错过除“权限被拒绝”之外的其他错误的风险。
“权限被拒绝”可能是运行 时最常见的错误find
,但它远非唯一可能的错误,如果确实发生了另一个错误,您可能希望了解它。特别是,find
如果不存在起始点,则报告“没有这样的文件或目录”。如果有多个起始点,find
仍可能返回一些有用的结果并似乎有效。例如,如果a
和c
存在但b
不存在,则find a b c -name x
打印 中的结果a
,然后 中的“没有这样的文件或目录” b
,然后导致c
。
将 stdout 和 stderr 合并为 stdout 并通过管道传输grep
或其他命令来过滤它(如使用或2>&1 | grep ...
)|& grep ...
可能会无意中过滤掉名称中包含被过滤消息的文件。
例如,如果您过滤掉包含“Permission denied”的行,那么您还会删除显示“Permission denied messages.txt”等文件名的搜索结果。这种情况可能是偶然发生的,但也有可能文件被赋予一个特制的名称来阻止您的搜索。
过滤合并的流还有另一个问题,即不能通过更有选择性的过滤来缓解(例如在grep -vx 'find: .*: Permission denied'
管道的右侧)。某些find
操作(包括-print
未指定任何操作时隐式执行的操作)会根据是否标准输出是一个终端。
- 如果它不是终端,则文件名将按原样输出,即使它们包含可能改变终端行为的奇怪字符(如换行符和控制字符)。如果它是终端,那么这些字符将被抑制并被
?
打印出来。 - 这通常是您想要的。如果您要进一步处理文件名,则必须按原样输出它们。但是,如果您要显示它们,带有换行符的文件名可能会模仿多个文件名,而带有退格符序列的文件名可能会看起来像不同的名称。还可能出现其他问题,例如文件名包含会更改终端颜色的转义序列。
grep
但是通过另一个命令(如)管道传输搜索结果会导致find
不再看到终端。(更准确地说,它导致其标准输出不是终端。)然后输出奇怪的字符。但如果管道右侧的所有命令都(A)删除看起来像“权限被拒绝”消息的行并(二)find
打印剩下的内容,那么您仍然会受到终端检测旨在防止的那种恶作剧的影响。- 请参阅不寻常的文件名部分
man find
以获取更多信息,包括打印文件名的每个操作的行为。(“Find 的许多操作都会导致打印其他用户控制的数据...”) 另请参阅章节3.3.2.1,3.3.2.2, 和3.3.2.3的GNU Findutils 参考手册。
上述关于不寻常文件名的讨论涉及GNU 查找,这是find
包括 Ubuntu 在内的 GNU/Linux 系统中的实现。
过滤标准错误时保留标准输出
你真的想要在这里离开标准输出管道完好无损标准错误到grep
。不幸的是,没有简单的语法来实现这一点。|
管道 stdout,一些 shell(包括bash
)支持|&
管道两个流 — 或者您可以先用 将 stderr 重定向到 stdout 2>&1 |
,这具有相同的效果。但常用的 shell 不提供仅管道 stderr 的语法。
你仍然可以这样做。只是有点尴尬。一种方法是将 stdout 与 stderr 交换,以便搜索结果在 stderr 上,错误在 stdout 上,然后将 stdout 管道传输到grep
进行过滤:
find args 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied'
通常,你会将参数传递给find
,例如起点(搜索的位置,通常是目录)和谓词(测试和操作)。它们代替args
多于。
这通过引入新的文件描述符保留要交换的两个标准流之一,执行重定向来交换它们,并关闭新的文件描述符。
- 文件描述符 1 是 stdout,2 是 stderr(未重定向的 0 是标准输入)。但您也可以使用其他文件描述符进行重定向。这可用于打开或保持打开文件或设备。
3>&1
将文件描述符 3 重定向到 stdout,以便当 stdout(文件描述符 1)随后被重定向时,仍然可以轻松地写入原始 stdout。1>&2
将 stdout 重定向到 stderr。由于文件描述符 3 仍然是原始 stdout,因此仍然可以访问它。2>&3
将 stderr 重定向到文件描述符 3,即原始 stdout。3>&-
关闭不再需要的文件描述符 3。- 有关详细信息,请参阅For more information, see如何通过管道传输 stderr 而不是 stdout?和IO 重定向 - 交换 stdout 和 stderr(高级)尤其是仅通过过滤器传输 stderr。
然而,这种方法的缺点是搜索结果发送到 stderr,错误发送到 stdout。如果您直接在交互式 shell 中运行此命令,并且没有进一步通过管道或重定向输出,那么这实际上并不重要。否则,这可能是一个问题。如果您将该命令放在脚本中,然后有人(可能是您,稍后)重定向或通过管道传输其输出,则不会表现符合预期。
解决办法是过滤完输出后,将流交换回去。在管道右侧应用上面显示的相同重定向无法实现此目的,因为|
只有管道 stdout,因此管道的右侧仅接收最初发送到 stderr 的输出(因为流已交换)而不是原始 stdout 输出。相反,您可以使用(
)
运行上述命令在子 shell 中(有关的),然后对其应用交换重定向:
(find args 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
正是分组(而不是子 shell)实现了这一点。如果您愿意,可以使用{
;}
:
{ find args 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied'; } 3>&1 1>&2 2>&3 3>&-
一种不太麻烦的方法:流程替代
一些 shell,包括支持 Bash 的系统(包括像 Ubuntu 这样的 GNU/Linux 系统),允许你执行流程替代,它允许您运行命令并重定向到/从其流之一。您可以将find
命令的 stderr 重定向到grep
过滤它的命令,并将该grep
命令的 stdout 重定向到 stderr。
find args 2> >(grep -Fv 'Permission denied' >&2)
功劳归于到 Android 开发对于这个想法。
- 也可以看看Pinko 的回答到如何通过管道传输 stderr 而不是 stdout?
- 这两个例子都使用了
1>&2
。我使用过>&2
,这相当于(有关的)。使用你喜欢的任意一个。
虽然bash
支持流程替代,sh
在 Ubuntu 中是dash
,但实际上没有。如果您尝试使用此方法,它会给出“语法错误:重定向意外”,而交换 stdout 和 stderr 的方法仍然有效。此外,当bash
在POSIX 模式,对进程替换的支持已关闭。
bash
在 POSIX 模式下运行的一种情况是当它被调用为sh
1bash
。因此,在 Fedora 等提供 的操作系统上/bin/sh
,或者如果您在 Ubuntu 上将/bin/sh
符号链接指向bash
自己,则进程替换在脚本中仍然不起作用sh
,除非事先命令关闭 POSIX 模式。如果您想在脚本中使用此方法,最好的办法是将#!/bin/bash
在顶部而不是#!/bin/sh
,如果您还没有的话。
1:在这种情况下,bash
自动打开 POSIX 模式后它运行其启动脚本中的命令。
一个例子
能够测试这些命令很有用。为此,我创建了tmp
当前目录的一个子目录,并在其中填充了一些文件和目录,并从其中一个目录中删除权限,以触发“权限被拒绝”错误find
。
mkdir tmp; cd tmp; mkdir a b c; touch w a/x 'a/Permission denied messages.txt' b/y c/z; chmod 0 b
其中一个目录是 accessible includes a file with "Permission denied" in its name. Running find
with no redirections or pipes shows this file, but also shows the actual "Permission denied" error for another directory that isn't accessible:
ek@Io:~/tmp$ find
.
./a
./a/Permission denied messages.txt
./a/x
./c
./c/z
./w
./b
find: ‘./b’: Permission denied
Piping both stdout and stderr to grep
and filtering out lines that contain "Permission denied" makes the error message go away but also hides the search result for the file with that phrase in its name:
ek@Io:~/tmp$ find |& grep -Fv 'Permission denied'
.
./a
./a/x
./c
./c/z
./w
./b
find 2>&1 | grep -Fv 'Permission denied'
is equivalent and produces the same output.
The methods shown above for filtering out "Permission denied" only from error messages—and not from search results—are successful. For example, here's the method where stdout and stderr are swapped:
ek@Io:~/tmp$ (find 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
.
./a
./a/Permission denied messages.txt
./a/x
./c
./c/z
./w
./b
find args 2> >(grep -Fv 'Permission denied' >&2)
produces the same output.
You can trigger a different error message to ensure that lines sent to stderr that don't contain the text "Permission denied" are still allowed through. For example, here I have run find
with the current directory (.
) as one starting point, but the nonexistent directory foo
as another:
ek@Io:~/tmp$ (find . foo 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
.
./a
./a/Permission denied messages.txt
./a/x
./c
./c/z
./w
./b
find: ‘foo’: No such file or directory
Checking That find
's Standard Output is Still a Terminal
We can also see which commands cause special characters, such as newlines, to be displayed literally. (This can be done separately from the demonstration above, and it doesn't need to be in the tmp
directory.)
Make a file with a newline in its name:
touch $'abc\ndef'
Usually we use directories as starting points for find
, but files work too:
$ find abc*
abc?def
Piping stdout to another command causes the newline to be outputted literally, creating the false impression of two separate search results abc
and def
. We can test that with cat
:
$ find abc* | cat
abc
def
Redirecting just stderr does not cause this problem:
$ find abc* 2>/dev/null
abc?def
Nor does closing it:
$ find abc* 2>&-
abc?def
Piping to grep
does cause the problem:
$ find abc* |& grep -Fv 'Permission denied'
abc
def
(Replacing |&
with 2>&1 |
is equivalent and produces the same output.)
Swapping stdout and stderr and piping stdout does not cause the problem—find
's stdout becomes stderr, which is not piped:
$ find abc* 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied'
abc?def
Grouping that command and swapping the streams back does not cause the problem:
$ (find abc* 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
abc?def
(The {
;}
version produces the same output.)
Using process substitution to filter stderr does not cause the problem either:
$ find abc* 2> >(grep -Fv 'Permission denied' >&2)
abc?def