Как передать 2> / dev / null в качестве переменной?

13

У меня есть этот код, который работает:

# Hide irrelevant errors so chrome doesn't email us in cron
if [[ $fCron == true ]] ; then
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName" 2>/dev/null
else
    # Get silly error messages when running from terminal
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
fi

Если я попытаюсь сократить это так:

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
[[ $fCron == true ]] && HideErrors="2>/dev/null"

google-chrome --headless --disable-gpu --dump-dom \
    "$RobWebAddress" > "$DownloadName" "$HideErrors"

Я получаю сообщения об ошибках:

[0826/043058.634775:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.672587:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
[0826/043058.711640:ERROR:headless_shell.cc(597)] Open multiple tabs is only supported when remote debugging is enabled.
(... SNIP ...)

Почему жестко закодированный аргумент работает, а не аргумент как переменная?


Изменить 2:

В настоящее время я нашел успех с альтернативным предложением второго ответа:

# Redirect errors when cron is used to /dev/null to reduce emails
ErrorPipe=/dev/stderr
[[ $fCron == true ]] && ErrorPipe=/dev/null

google-chrome --headless --disable-gpu --dump-dom \
                "$RobWebAddress" > "$DownloadName" 2>"$ErrorPipe"

Изменить 1:

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

[[ $fCron != true ]] &&
    exec 2> >(grep -v 'GtkDialog mapped without a transient parent' >&2)
WinEunuuchs2Unix
источник
Вы можете попробовать [[ $fCron == true ]] && exec 2>/dev/nullвместо этого
Steeldriver
.. грубо говоря, это потому что оболочка устанавливает перенаправления перед расширением переменных, я думаю. См., Например, bash: используйте переменную для хранения перенаправления stderr | stdout
steeldriver

Ответы:

19

Причина, по которой вы не можете вызвать перенаправление при расширении, "$HideErrors"заключается в том, что подобные символы >не обрабатываются специально после создания расширения параметра . На самом деле это очень хорошо, потому что такие символы появляются в тексте, который вы можете расширить и использовать буквально.

Это касается того, цитируете ли вы или нет $HideErrors. Результат раскрытия параметров может быть разбит на слова и выделен, когда расширение не заключено в кавычки, но это так.


Что касается того, что с этим делать, существует множество способов добиться условного перенаправления. Для очень простой команды, может быть разумно написать целую команду дважды, один раз в каждой ветви caseили if- elseконструкт. Однако вскоре это становится обременительным, и команда, которую вы показали, безусловно, является идеальной.

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

Сохраните команду вместо перенаправления. Вместо того, чтобы пытаться сохранить перенаправление в переменной и применять расширение параметра, сохраните команду в функции оболочки . Затем напишите a caseили if- else, в котором функция вызывается с перенаправлением на одной ветви и без нее на другой.

Если вы представляете свою команду как код, который вы хотите написать один раз, но выполнить при нескольких обстоятельствах, то функция является естественным решением. Это то, что я обычно делаю. Преимущество в том, что он не требует ни вложенной оболочки, ни ручного хранения и сброса состояния.

С вашим кодом:

launch() {
    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
}

case $fCron in
true)  launch 2>/dev/null;;
*)     launch;; # Get silly error messages when running from terminal
esac

Вы можете применить любой интервал, который вам нравится, или if- elseвместо этого, если вы предпочитаете. Обратите внимание, что launchавтоматически используются вызывающая RobWebAddressи DownloadNameпеременные, даже если они являются локальными переменными, потому что Bash динамически ограничен , в отличие от большинства языков программирования с лексической областью.

Запустите команду в подоболочке и условно примените перенаправление к exec. Это то, что прокомментировал Steeldriver , но внутри, ( )чтобы сохранить эффект локальным . Когда встроенный запускаются без аргументов, она не заменяет текущую оболочку с новым процессом, но вместо этого применяет какое - либо из его перенаправлений к текущей оболочке.exec

(Также возможно отследить, что было стандартной ошибкой, и восстановить ее, не используя подоболочку и, следовательно, не жертвуя возможностью изменять среду текущей оболочки. Однако я оставлю подробности об этом другим ответам.)

С вашим кодом:

(
    # Suppress silly error messages unless running from terminal
    case $fCron in true) exec 2>/dev/null;; esac

    google-chrome --headless --disable-gpu --dump-dom \
        "$RobWebAddress" > "$DownloadName"
)

После закрытия )стандартная ошибка фактически восстанавливается до того, что было раньше, потому что она действительно перенаправляется только в подоболочку, а не в родительскую оболочку. Это также хорошо работает с существующими переменными оболочки, так как подоболочки получают их копию. Хотя я предпочитаю использовать функцию оболочки, я допускаю, что этот метод может потребовать меньше кода.

Оба метода работают независимо от того, с какого файла начинается стандартная ошибка файла или устройства, в том числе в случае перенаправлений, применяемых к функциям оболочки, которые вызывают код, содержащий условное поведение, а также в случае (упоминавшемся в вашем редактировании), где стандартная ошибка для весь сценарий уже был перенаправлен предыдущим или . То, что путь был создан путем подстановки процесса, не проблема.exec 2>&fdexec 2> path

Элия ​​Каган
источник
К вашему сведению SteelDriver упомянул что-то о execне знаю, планирует ли он ответ на этот вопрос ...
WinEunuuchs2Unix
@ WinEunuuchs2Unix Я надеюсь, что такой ответ все еще размещен. Хотя я в основном рекомендую использовать функцию, я также включил метод, включающий перенаправление на exec. Но, как я уже упоминал в скобках, я не рассматривал более сложные приложения, в которых старый файловый дескриптор хранится и восстанавливается без подоболочки. Я также не рассмотрел менее сложные приложения, такие как просто сохранение перенаправления, если это конец сценария. Другой ответ, если он будет опубликован, может охватить и то, и другое.
Элия ​​Каган
Я обновил свой вопрос существующим, execкоторый, я думаю, не должен влиять на ваш ответ.
WinEunuuchs2Unix
@ WinEunuuchs2Unix Да, это не должно быть проблемой. Я добавил параграф об этом в конце ответа.
Элия ​​Каган,
Интересное откровение, читающее ваш ответ, RobWebAddressопределенно является глобальным контекстом. DownloadNameбыл определен локально, но должен иметь глобальный контекст. По какой-то причине дочерние функции наследуют локальные определения родителей (чтобы DownloadNameбыла видна DownloadAsHTML ()функция, вызываемая UpdateOne ()функцией, которая определяла ее как локальную. Это был
тяжелый
4

Почему жестко закодированный аргумент работает, а не аргумент как переменная?

Потому что элементы синтаксиса не интерпретируются из расширенных значений переменных. То есть расширение переменной - это не то же самое, что замена ссылки на переменную текстом переменной в командной строке. ( Такие вещи , как ;, |, &&и цитаты и т.д., также не спец в значениях переменных.)

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

Псевдонимы - это просто замена текста, поэтому они могут содержать синтаксические элементы, такие как операторы и ключевые слова. В сценарии это необходимо shopt expand_aliases, поскольку по умолчанию они отключены в неинтерактивных оболочках. Итак, это печатает 2(только):

#!/bin/bash
shopt -s expand_aliases

alias redir='> /dev/null'
redir echo 1
alias redir=''
redir echo 2

(А также вы могли бы alias jos=if niin=then soj=fiи тогда написать все свои операторы if на финском языке. Я уверен, что любой, кто читает сценарий, полюбит вас.)

В качестве альтернативы всегда пишите перенаправление, но управляйте только целью с помощью переменной. Вам понадобится неоперативная цель для случая, когда вы не хотите менять направление вывода, но /dev/stderrдолжны работать в этом случае. На самом деле, добавление 2> /dev/stderrне является запретом из-за того, как Linux рассматривает открытые файлы /proc/<pid>/fdкак независимые от оригинала. Это влияет на расположение позиции записи и испортит вывод, если он перейдет в обычный файл.

Однако он должен работать в режиме добавления (или если stderr идет в канал или в терминал):

#!/bin/sh
exec 2>/tmp/error.log
dst=/dev/null
ls -l /nosuchfile-1 2>> "$dst"     # this doesn't print
dst=/dev/stderr
ls -l /nosuchfile-2 2>> "$dst"
ls -l /nosuchfile-3 2>> "$dst"

Так что повторюсь: 2> /dev/stderrможет сломаться.

ilkkachu
источник
Хахаха, теперь я буду использовать только финский ifs на работе. :>
десерт
Мне нравится альтернативное предложение. Мысль о expand_aliasesтом, страшно, потому что ~/.bashrcя думаю, что ваша программа может быть заложником .
WinEunuuchs2Unix
1
@ WinEunuuchs2Unix, да, expand_aliasesэто немного страшно. Но это ~/.bashrcне должно быть проблемой, поскольку он читается только интерактивными оболочками, .profileа друзья, которые могут его вызывать, читаются только оболочками входа в систему. Неинтерактивные оболочки без входа в систему, такие как сценарии, не должны запускать ни одну из них. (Но есть и $BASH_ENV, по-видимому .bashrc, читается, если stdin подключен к сетевому сокету. Насколько запутанным он может быть ...)
ilkkachu
Хорошо, я прекрасно понимаю ваше альтернативное предложение и попробую сегодня вечером :)
WinEunuuchs2Unix
Честно говоря, я не уверен, каким образом я бы это реализовал, если бы пришлось. Вероятно, я бы сохранил команду в функции или массиве, а затем разветвил, чтобы решить, следует ли поместить туда перенаправление (использование функции было показано в другом ответе). Или этот 2>> "$dst"трюк, но я только что понял, что он не работает в общем случае, поэтому лучше быть осторожным с этим.
ilkkachu
1

Заголовок вопроса: «Как передать 2> / dev / null в качестве переменной?» Это может быть сделано с помощьюeval

joshua@nova:/tmp$ X=">/dev/null"
joshua@nova:/tmp$ echo $X
>/dev/null
joshua@nova:/tmp$ eval echo $X
joshua@nova:/tmp$ eval echo hi
hi
joshua@nova:/tmp$ eval echo hi $X
joshua@nova:/tmp$ echo hi $X
hi >/dev/null
joshua@nova:/tmp$ 

Таким образом, мы можем переписать как

# Hide irrelevant errors so chrome doesn't email us in cron
local HideErrors
local RobWebAddress2
local DownloadName2
[[ $fCron == true ]] && HideErrors="2>/dev/null"
RobWebAddress2='"$RobWebAddress"'
DownloadName2='>"$DownloadName"'

eval google-chrome --headless --disable-gpu --dump-dom \
    $RobWebAddress2 $DownloadName2 "$HideErrors"

Где косвенный доступ к переменным предотвращает слишком быстрое расширение в остальной части командной строки.

Двойные кавычки в переменных работают просто отлично.

joshua@nova:/tmp$ X='"'
joshua@nova:/tmp$ Y='$X'
joshua@nova:/tmp$ eval echo $Y
"
joshua@nova:/tmp$ 
Джошуа
источник
@EliahKagan: Заголовок вопроса: «Как передать 2> / dev / null в качестве переменной?»
Джошуа
Хорошо, это не работает. Я исправил это.
Джошуа
Теперь файл всегда именуется DownloadName- и буквенный текст RobWebAddressвсегда используется для URL. Вы используете $" "цитаты . Я думаю, что это может быть непреднамеренным, и вы можете захотеть $внутри " ", но вы сделали это таким образом в обоих местах, так что я не уверен. Я думаю, что просто > "$DownloadName"должен это исправить. Но я понимаю, что вам это может не понравиться, поскольку случайное смешение аргументов и не-аргументов evalявляется одной из причин, по которой так опасно и не рекомендуется использовать evalконкатенирующее поведение.
Элия ​​Каган
@EliahKagan: Ой. Моя предпочтительная оболочка для сценариев не имеет кавычек "".
Джошуа
1
Если вы исправите это, оно должно работать. И я всегда ошибался, думая, что он вставил цитаты в произвольный текст! Он строит буквальные аргументы, которые evalобъединяются перед оценкой. Но я думаю, что другой способ выразить это - это запутанный способ написания, eval 'google-chrome --headless --disable-gpu --dump-dom "$RobWebAddress" > "$DownloadName" '"$HideErrors"который напоминает внешний вид кода OP. И вообще, использовать evalдля задач, которые не нужны, это плохо . (Ничего из этого не оправдывает - и даже не объясняет - ошибочность и враждебность моего старого ответа.)
Элия ​​Каган