取出“拒绝访问”行
当我使用find
查看/home
目录中的所有pdf文件时,我看到access denied
。 为了消除它们,我试过:
find /home -iname "*.pdf" | grep -v "access denied"
但是,结果是一样的。 我怎么能摆脱这些线?
您尝试的操作不起作用,因为access denied
输出是错误并在STDERR上发送,而不是通过管道传输到grep
的STDOUT。
您可以通过仅重定向STDERR来避免看到这些错误
find /home -iname "*.pdf" 2>/dev/null
或者正如David Foerster评论的那样,我们可以更简洁地关闭 STDERR
find /home -iname "*.pdf" 2>&-
但是,我怀疑你实际上只想搜索你的家而不是其他用户,所以也许你真的想要
find ~ -iname "*.pdf"
如果这会引发错误,那么您的本地配置中可能存在一些错误的所有权,您应该对其进行调查。
访问被拒绝可能是在stderr
而不是stdout
上打印的。
试试这个:
find /home -iname "*.pdf" 2>&1 | grep -v "access denied"
2>&1
将输出从stderr
重定向到stdout
,这样grep -v
就能完成它的工作。 (默认情况下, |
仅管道stdout
而不是stderr
。)
您可能意味着“权限被拒绝” – 这是Ubuntu中的find
,当您因为文件权限而无法访问某些内容而不是“访问被拒绝”时向您显示。
一个完全通用的命令,正确地执行此操作(并且,作为奖励,只要错误消息相同,可以移植到其他* nix es):
(find 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-
(通常你想传递一些参数来find
。那些在第一次重定向3>&1
。)
但是,通常你可以使用更简单的东西。 例如,您可以使用进程替换 。 细节如下。
最常见的方法及其局限性
两种典型的方法是抛弃stderr (如Zanna的回答 )或将stderr重定向到stdout并过滤stdout(如Android Dev的答案 )。 虽然它们具有易于编写且通常是合理选择的优点,但这些方法并不理想。
丢弃发送到stderr的所有内容 – 例如通过将其重定向到带有2>/dev/null
的空设备 ,或者将其关闭为2>&-
运行除“权限被拒绝”之外的错误丢失的风险。
“权限被拒绝”可能是运行find
时最常见的错误,但它远非唯一可能的错误,如果确实发生了其他错误,您可能想知道它。 特别是,如果起点不存在,则find
“无此类文件或目录”报告。 有了多个起点, find
仍然可以返回一些有用的结果并且看起来有效。 例如,如果a
和c
存在但b
不存在,则find abc -name x
结果打印为b
,然后为b
“No such file or directory”,然后生成c
。
将stdout和stderr一起组合到stdout并将其管道到grep
或其他命令来过滤它 – 就像2>&1 | grep ...
2>&1 | grep ...
或|& grep ...
– 存在无意中过滤掉名称中包含要过滤的消息的文件的风险。
例如,如果您筛选出包含“权限被拒绝”的行,那么您还将删除显示文件名的搜索结果,如“Permission denied messages.txt”。 这可能是偶然发生的,但也可能会为文件提供一个特制的名称来阻止您的搜索。
过滤组合流有另一个问题, 无法通过更有选择性地过滤来缓解 (例如在管道右侧使用grep -vx 'find: .*: Permission denied'
)。 有些find
操作,包括指定无操作时隐式的-print
操作,根据stdout是否为终端确定如何输出文件名。
- 如果它不是终端,则文件名按原样输出,即使它们包含奇怪的字符,如换行符和控制字符,可能会改变终端的行为。 如果是终端,那么这些字符会被抑制并且
?
而是印刷。 - 这通常是你想要的。 如果要进一步处理文件名,则必须按字面输出。 但是,如果要显示它们,带有换行符的文件名可能会模仿多个文件名,并且带有一系列退格符的文件名可能看起来是一个不同的名称。 其他问题也是可能的,例如包含更改终端颜色的转义序列的文件名。
- 但是通过另一个命令(如
grep
)来管理搜索结果会导致find
不再查看终端。 (更确切地说,它导致它的stdout不是终端。)然后字面上输出奇怪的字符。 但是,如果管道右侧的所有命令都是(a)删除看起来像“权限被拒绝”消息的行和(b)打印剩下的那些,那么你仍然会受到那种find
的那些恶作剧的影响。终端检测旨在防止。 - 有关详细信息,请参阅
man find
的“exception文件”部分,包括打印文件名的每个操作的行为。 ( “查找的许多操作导致打印其他用户控制的数据……” )另请参阅GNU Findutils参考手册的3.3.2.1,3.3.2.2和3.3.2.3节。
上面讨论的exception文件名属于GNU find ,它是GNU / Linux系统中的find
实现,包括Ubuntu。
过滤标准误差时单独保留标准输出
你真正想要的是将stdout保持原样,同时将stderr送到grep
。 不幸的是,没有简单的语法。 |
管道stdout,以及一些shell(包括bash
)支持|&
管道两个流 – 或者你可以先用2>&1 |
将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是stdin )。 但您也可以使用其他文件描述符重定向。 这可用于打开或保持打开文件或设备。
-
3>&1
将文件描述符3重定向到stdout,这样当stdout(文件描述符1)随后被重定向时,仍然可以轻松地写入原始标准输出。 -
1>&2
将stdout重定向到stderr。 由于文件描述符3仍然是原始标准输出,因此仍然可以访问它。 -
2>&3
将stderr重定向到文件描述符3,这是原始标准输出。 -
3>&-
关闭不再需要的文件描述符3。 - 有关更多信息,请参阅如何管道stderr,而不是stdout? 和IO重定向 – 交换stdout和stderr(高级) ,特别是通过filter只管道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 Dev 。
- 另见Pinko对如何管道stderr 的回答 ,而不是stdout?
- 这两个例子都使用
1>&2
。 我使用了>&2
, 这是等价的 ( 相关的 )。 使用您喜欢的任何一种。
虽然bash
支持进程替换,但是Ubuntu中的sh
是dash
,而不是。 如果您尝试使用此方法,它将为您提供“语法错误:重定向意外”,而交换stdout和stderr的方法仍然有效。 此外,当bash
在POSIX模式下运行时,将关闭对进程替换的支持。
bash
在POSIX模式下运行的一种情况是它被调用为sh
1 。 因此,在像Fedora这样的操作系统上, bash
提供/bin/sh
,或者如果你已经使/bin/sh
符号链接点指向自己在Ubuntu上进行攻击,那么进程替换仍然无法在sh
脚本中工作,没有先前的命令转POSIX模式关闭。 你最好的选择,如果你想在脚本中使用这个方法,那就是把#!/bin/bash
放在顶部而不是#!/bin/sh
,如果你还没有。
1 :在这种情况下, bash
在其启动脚本中运行命令后会自动打开POSIX模式。
一个例子
能够测试这些命令很有用。 为此,我创建了当前目录的tmp
子目录,并用一些文件和目录填充它,从其中一个获取权限以在find
触发“权限被拒绝”错误。
mkdir tmp; cd tmp; mkdir abc; touch wa/x 'a/Permission denied messages.txt' b/yc/z; chmod 0 b
其中一个可访问的目录包括名称中包含“Permission denied”的文件。 运行find
没有重定向或管道显示此文件,但还显示另一个无法访问的目录的实际“权限被拒绝”错误:
ek@Io:~/tmp$ find . ./a ./a/Permission denied messages.txt ./a/x ./c ./c/z ./w ./b find: './b': Permission denied
将stdout和stderr管道化为grep
并过滤掉包含“Permission denied”的行会使错误消息消失,但也会在其名称中隐藏具有该短语的文件的搜索结果:
ek@Io:~/tmp$ find |& grep -Fv 'Permission denied' . ./a ./a/x ./c ./c/z ./w ./b
find 2>&1 | grep -Fv 'Permission denied'
find 2>&1 | grep -Fv 'Permission denied'
是等效的,并产生相同的输出。
上面显示的用于仅从错误消息中过滤掉“Permission denied”而非搜索结果的方法是成功的。 例如,这是交换stdout和stderr的方法:
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)
产生相同的输出。
您可以触发不同的错误消息,以确保仍然允许发送到stderr 但不包含“Permission denied”文本的行。 例如,在这里我运行find
当前目录( .
)作为一个起点,但不存在的目录foo
作为另一个:
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
检查find
的标准输出仍然是终端
我们还可以看到哪些命令会导致特殊字符(例如换行符)按字面显示。 (这可以与上面的演示分开完成,并且不需要在tmp
目录中。)
在其名称中创建一个带换行符的文件:
touch $'abc\ndef'
通常我们使用目录作为find
起点,但文件也可以工作:
$ find abc* abc?def
将stdout管道到另一个命令会导致换行按字面输出,从而产生两个单独搜索结果abc
和def
的错误印象。 我们可以用cat
测试一下:
$ find abc* | cat abc def
重定向stderr不会导致此问题:
$ find abc* 2>/dev/null abc?def
关闭它也不会:
$ find abc* 2>&- abc?def
管道到grep
会导致问题:
$ find abc* |& grep -Fv 'Permission denied' abc def
(替换|&
with 2>&1 |
是等效的,并产生相同的输出。)
交换标准输出和stderr和管道标准输出不会导致问题 – find
的标准输出变为stderr,而不是管道输出:
$ find abc* 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied' abc?def
对该命令进行分组并将流交换回来不会导致问题:
$ (find abc* 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&- abc?def
( {
;}
版本生成相同的输出。)
使用进程替换来过滤stderr也不会导致问题:
$ find abc* 2> >(grep -Fv 'Permission denied' >&2) abc?def