取出“拒绝访问”行

当我使用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仍然可以返回一些有用的结果并且看起来有效。 例如,如果ac存在但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中的shdash ,而不是。 如果您尝试使用此方法,它将为您提供“语法错误:重定向意外”,而交换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管道到另一个命令会导致换行按字面输出,从而产生两个单独搜索结果abcdef的错误印象。 我们可以用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