Удаление линий «Доступ запрещен»

9

Когда я использую, findчтобы увидеть все файлы PDF в /homeкаталоге, я вижу access denied. Чтобы устранить их, я попытался:

find /home -iname "*.pdf" | grep -v "access denied"

Тем не менее, результат тот же. Как я могу избавиться от этих строк?

solfish
источник

Ответы:

19

То, что вы пробовали, не сработало, потому что access deniedвыходные данные являются ошибками и отправляются на STDERR вместо STDOUT, который передается по каналу grep.

Вы можете избежать этих ошибок, перенаправляя только STDERR

find /home -iname "*.pdf" 2>/dev/null

Или, как прокомментировал Дэвид Фёрстер, мы можем более кратко закрыть STDERR

find /home -iname "*.pdf" 2>&-

Тем не менее, я подозреваю, что вы на самом деле хотите искать свой дом, а не других пользователей, так что, возможно, вы действительно хотите

find ~ -iname "*.pdf"

Если это приводит к ошибкам, в вашей локальной конфигурации могут быть некоторые неправильные владельцы, которые вы должны исследовать.

Занна
источник
3
Гррр, почему люди всегда бьют меня на 30 секунд? : \
YouAGitForNotUsingGit
find: «/home/ihsan/.gvfs»: доступ запрещен find: «/home/ihsan/.dbus»: доступ запрещен для команды ~
solfish
что-то не так для этого? да, я также хочу, чтобы домашний каталог других пользователей также создавался мной для тестирования
solfish
2
@ Solfish Насколько я знаю, эти файлы должны принадлежать вам. Вы можете захотетьsudo chown $USER: ~/.gvfs ~/.dbus
Zanna
1
Этого должно быть достаточно, чтобы закрыть stderr 2>&-. GNU find не завершит свою работу, если попытается записать сообщения об ошибках в дисфункциональный файловый дескриптор. Поскольку проблемы собственности sudo chown -R $USER: ...были бы более эффективными в случае большего количества файлов в пределах, которые не принадлежат $USER.
Дэвид Фёрстер
8

Отказано в доступе, вероятно, печатается, stderrа не stdout.

Попробуй это:

find /home -iname "*.pdf" 2>&1 | grep -v "access denied"

2>&1Перенаправляет вывод stderrна stdout, так что grep -vможет делать свою работу. (По умолчанию |только трубы, stdoutа не stderr.)

You'reAGitForNotUsingGit
источник
но для этого 2> & 1 означает, что если stderr существует, отправьте в stdout?
Solfish
@ solfish Да, в этом-то и дело :)
You'reAGitForNotUsingGit
то, что я не понимаю, это до "|" в качестве выхода; мы получили только stderr правильно? и после "|" в качестве входных данных мы получили это
Solfish
@solfish Ну, я столкнулся с этой проблемой около полутора лет назад, и я смог решить ее другим способом . Но затем комментарий под моим ответом предложил просто использовать 2>&1... Я не эксперт по bash, поэтому, если это неверно, пожалуйста, скажите так :)
You'reAGitForNotUsingGit
@AndroidDev Я предлагаю добавить этот другой метод к этому ответу в качестве альтернативы. Критика Итана Рейснера заключалась в том, что процесс замещения не переносим. Но bashв Ubuntu он есть, кроме как в режиме POSIX . Я думаю, что это лучшее решение - файл со злонамеренным именем access deniedвсе равно появится.
Элия ​​Каган
4

Вы, вероятно, имеете в виду «Отказано findв доступе » - это то, что в Ubuntu показывает вам, когда вы не можете получить доступ к чему-либо из-за прав доступа к файлу, а не «доступ запрещен».

Одна полностью общая команда, которая делает это правильно (и, в качестве бонуса, переносима на другие * nix- ы, при условии, что сообщение об ошибке совпадает):

(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 a b c -name xпечатает , выдает результат a, затем «Нет такого файла или каталога» b, а затем - c.

Объединение stdout и stderr вместе в stdout и передача его в grepили к какой-либо другой команде для его фильтрации - как с 2>&1 | grep ...или - |& grep ...исключает риск непреднамеренной фильтрации файла, имя которого содержит фильтруемое сообщение.

Например, если вы отфильтровываете строки, содержащие «Отказано в доступе», вы также отбрасываете результаты поиска с именами файлов, такими как «Отказано в доступе messages.txt». Это может произойти случайно, хотя для файла также может быть задано специально созданное имя, чтобы помешать вашему поиску.

Фильтрация объединенных потоков имеет еще одну проблему, которая не может быть уменьшена путем более избирательной фильтрации (например, с grep -vx 'find: .*: Permission denied'правой стороны канала). Некоторые findдействия, включая -printнеявное действие, когда вы не указываете никаких действий, определяют, как выводить имена файлов на основе того, является ли stdout терминалом.

  • Если это не терминал, то имена файлов выводятся как есть, даже если они содержат странные символы, такие как символы новой строки и управляющие символы, которые могут изменить поведение вашего терминала. Если это терминал, то эти символы подавляются и ?печатаются вместо этого.
  • Обычно это то, что вы хотите. Если вы собираетесь обрабатывать имена файлов дальше, они должны выводиться буквально. Однако, если вы собираетесь их отображать, в противном случае имя файла с новой строкой может имитировать несколько имен файлов, а имя файла с последовательностью символов возврата может быть другим именем. Возможны и другие проблемы, такие как имена файлов, содержащие escape-последовательности, которые меняют цвета в вашем терминале.
  • Но передача результатов поиска с помощью другой команды (например grep) приводит findк тому, что терминал больше не видит. (Точнее, это приводит к тому, что его стандартный вывод не является терминалом.) Тогда странные символы выводятся буквально. Но если вся команда на правой стороне канала выполняет (а) удаление строк, которые выглядят как сообщения «Отказано в доступе», и (б) выводит то, что осталось, то вы все еще подвержены подобным махинациям, которые являются findтерминальными. обнаружение предназначено для предотвращения.
  • Смотрите раздел НЕОБЫЧНЫЕ ФИЛЬМЫ man findдля получения дополнительной информации, в том числе о поведении каждого из действий, которые печатают имена файлов. ( «Многие из действий find приводят к печати данных, которые находятся под контролем других пользователей ...» ) См. Также разделы 3.3.2.1 , 3.3.2.2 и 3.3.2.3 справочного руководства по GNU Findutils .

Вышеупомянутое обсуждение необычных имен файлов относится к GNU find , который является findреализацией в системах GNU / Linux, включая Ubuntu.

Оставить один стандартный вывод при фильтрации стандартной ошибки

То , что вы действительно хотите здесь , чтобы оставить стандартный вывод нетронутым , а обжигающе STDERR к grep. К сожалению, для этого нет простого синтаксиса. |pipe stdout и некоторые оболочки (в том числе bash) поддерживают |&передачу обоих потоков - или вы можете сначала перенаправить stderr на stdout 2>&1 |, что дает тот же эффект. Но обычно используемые оболочки не предоставляют синтаксис только для pipe stderr.

Вы все еще можете сделать это. Это просто неловко. Один из способов - поменять стандартный вывод с помощью 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 на стандартный вывод, так что при последующем перенаправлении стандартного вывода (дескриптор файла 1) исходный стандартный вывод все еще может быть легко записан.
  • 1>&2перенаправляет стандартный вывод в стандартный поток ошибок Поскольку файловый дескриптор 3 по-прежнему является исходным стандартным выводом, к нему можно получить доступ.
  • 2>&3 перенаправляет stderr в файловый дескриптор 3, который является исходным stdout.
  • 3>&- закрывает файловый дескриптор 3, который больше не нужен.
  • Для получения дополнительной информации см. Как передать stderr, а не stdout? и перенаправление ввода-вывода - обмен stdout и stderr (Advanced) и особенно труба только stderr через фильтр .

Однако этот метод имеет тот недостаток, что результаты поиска отправляются в stderr, а ошибки отправляются в stdout . Если вы запускаете эту команду непосредственно в интерактивной оболочке, а не отправляете и не перенаправляете вывод, это не имеет значения. В противном случае это может быть проблемой. Если вы поместите эту команду в сценарий, а затем кто-то (возможно, вы позже) перенаправит или передаст ее вывод, она не будет работать так, как ожидалось .

Решение состоит в том, чтобы поменять потоки обратно после того, как вы закончите фильтровать вывод . Применение тех же перенаправлений, показанных выше на правой стороне конвейера, не достигнет этого, потому что |только канал stdout, так что эта сторона конвейера получает только выходные данные, которые были первоначально отправлены в stderr (потому что потоки были поменяны местами), а не оригинал стандартный вывод. Вместо этого вы можете использовать ( )для запуска вышеупомянутой команды в подоболочке ( связанной ), а затем применить перенаправления перестановки к этому:

(find args 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied') 3>&1 1>&2 2>&3 3>&-

Это группировка, а не конкретная оболочка, которая делает эту работу. Если вы предпочитаете, вы можете использовать { ;}:

{ find args 3>&1 1>&2 2>&3 3>&- | grep -Fv 'Permission denied'; } 3>&1 1>&2 2>&3 3>&-

Менее громоздкий путь: замена процесса

Некоторые оболочки, включая Bash в системах, которые могут его поддерживать (включая системы GNU / Linux, такие как Ubuntu), позволяют выполнять подстановку процессов , что позволяет вам запускать команду и перенаправлять в / из одного из его потоков. Вы можете перенаправить findstderr grepкоманды в команду, которая ее фильтрует, и перенаправить стандартный grepвывод этой команды в stderr.

find args 2> >(grep -Fv 'Permission denied' >&2)

Заслуга в Android Dev для этой идеи.

Хотя bash поддерживает подстановку процессов, shв Ubuntu есть dash, чего нет. Если вы попытаетесь использовать этот метод, он выдаст «Синтаксическая ошибка: неожиданное перенаправление», в то время как метод обмена stdout и stderr по-прежнему будет работать. Кроме того, при работе bashв режиме POSIX поддержка подстановки процессов отключена.

Одна ситуация, когда bashработает в режиме POSIX, это когда он вызывается как sh1 . Таким образом, в такой операционной системе, как Fedora, где она bashпредоставляется /bin/sh, или если вы /bin/shуказали символическую ссылку на bashсебя в Ubuntu, подстановка процесса по-прежнему не работает в 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

Один из каталогов, является доступным включает в себя файл с «Отказано в доступе» в его названии. Бег 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' эквивалентно и дает тот же результат.

Методы, показанные выше для фильтрации «Отказано в доступе» только из сообщений об ошибках, а не из результатов поиска, являются успешными. Например, вот метод, в котором 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, которые не содержат текст «Отказано в доступе», по-прежнему пропускаются. Например, здесь я запустил 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

(Замена |&на 2>&1 |эквивалентную и дает тот же результат.)

Замена stdout и stderr и stdout по конвейеру не вызывает проблемы find- stdout становится 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
Элия ​​Каган
источник