Подавить трассировку выполнения команды echo?

29

Я запускаю сценарии оболочки от Jenkins, которые запускают сценарии оболочки с опциями shebang #!/bin/sh -ex.

По словам Баш Шебанга для чайников? , -x«Заставляет оболочку напечатать трассировки выполнения», который отлично подходит для большинства целей - за исключением эху:

echo "Message"

производит вывод

+ echo "Message"
Message

что немного избыточно, и выглядит немного странно. Есть ли способ оставить -xвключенным, но только вывод

Message

вместо двух строк выше, например, с помощью префикса команды echo со специальным символом команды или перенаправления вывода?

Кристиан
источник

Ответы:

20

Когда вы по уши в аллигаторах, легко забыть, что целью было осушить болото.                   - народная поговорка

Вопрос в том echo, и все же большинство ответов до сих пор были сосредоточены на том, как проникнуть в set +xкоманду. Существует гораздо более простое, более прямое решение:

{ echo "Message"; } 2> /dev/null

(Я признаю, что, возможно, я не думал о том, { …; } 2> /dev/null если бы я не видел это в предыдущих ответах.)

Это несколько громоздко, но, если у вас есть блок последовательных echoкоманд, вам не нужно делать это для каждой из них в отдельности:

{
  echo "The quick brown fox"
  echo "jumps over the lazy dog."
} 2> /dev/null

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

Вы можете уменьшить нагрузку на типирование, используя идею kenorb -/dev/null постоянно открывать нестандартный файловый дескриптор (например, 3), а затем говорить 2>&3вместо того, чтобы 2> /dev/nullвсе время.


Первые четыре ответа на момент написания этой статьи требуют делать что-то особенное (и в большинстве случаев громоздкое) каждый раз, когда вы делаете echo. Если вы действительно хотите, чтобы все echo команды подавляли трассировку выполнения (а почему бы и нет?), Вы можете сделать это глобально, не тратя много кода. Во-первых, я заметил, что псевдонимы не отслеживаются:

$ myfunc()
> {
>     date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016  0:00:00 AM           # Happy Halloween!
$ myfunc
+ myfunc                                # Note that function call is traced.
+ date
Mon, Oct 31, 2016  0:00:01 AM
$ myalias
+ date                                  # Note that it doesn’t say  + myalias
Mon, Oct 31, 2016  0:00:02 AM

(Обратите внимание, что следующие фрагменты сценария работают, если shebang есть #!/bin/sh, даже если /bin/shэто ссылка на bash. Но, если она есть #!/bin/bash, вам нужно добавить shopt -s expand_aliasesкоманду, чтобы псевдонимы работали в сценарии.)

Итак, для моего первого трюка:

alias echo='{ set +x; } 2> /dev/null; builtin echo'

Теперь, когда мы говорим echo "Message", мы называем псевдоним, который не отслеживается. Псевдоним отключает параметр трассировки, подавляя при этом сообщение трассировки из setкоманды (используя технику, представленную сначала в ответе пользователя 5071535 ), а затем выполняет фактическую echoкоманду. Это позволяет нам получить эффект, аналогичный ответу пользователя 5071535, без необходимости редактировать код для каждой echo команды. Однако это оставляет режим трассировки выключенным. Мы не можем вставить set -xпсевдоним (или, по крайней мере, нелегко), потому что псевдоним позволяет заменить слово только строкой; никакая часть строки псевдонима не может быть введена в команду после аргументов (например, "Message"). Так, например, если скрипт содержит

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date

выход будет

+ date
Mon, Oct 31, 2016  0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016  0:00:04 AM           # Note that it doesn’t say  + date

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

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date


Было бы хорошо, если бы мы могли сделать set -xавтомат после echo- и мы можем, с немного более хитрым. Но прежде чем представить это, подумайте об этом. ОП начинается со сценариев, которые используют #!/bin/sh -exшебанг. Неявно пользователь может удалить xиз shebang и иметь скрипт, который работает нормально, без отслеживания выполнения. Было бы хорошо, если бы мы могли разработать решение, которое сохраняет это свойство. Первые несколько ответов здесь не соответствуют этому свойству, потому что они включают отслеживание «назад» после echoоператоров, безусловно, независимо от того, было ли оно уже включено.  Этот ответ явно не может распознать эту проблему, поскольку он заменяет echoвывод с выводом трассировки; поэтому все сообщения исчезают, если трассировка отключена. Сейчас я представлю решение, которое включает трассировку после условногоecho оператора - только если он уже был включен. Понижение этого до решения, которое безоговорочно включает отслеживание «назад», тривиально и оставлено в качестве упражнения.

alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
        builtin echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}

$-список опций; объединение букв, соответствующих всем установленным параметрам. Например, если eи xпараметры установлены, то $-будет нагромождением букв , которые включают в себя eи x. Мой новый псевдоним (выше) сохраняет значение $-перед выключением трассировки. Затем, с отключенной трассировкой, он передает управление в функцию оболочки. Эта функция выполняет фактическую echo проверку и затем проверяет, была ли xвключена опция при вызове псевдонима. Если опция была включена, функция снова включает ее; если он был выключен, функция выключает его.

Вы можете вставить вышеупомянутые семь строк (восемь, если вы включите shopt) в начале сценария, а остальные оставить в покое.

Это позволит вам

  1. использовать любую из следующих строк Шебанга:
    #! / bin / sh -ex
    #! / bin / sh -e
    #! / bin / sh –x
    или просто
    #! / Bin / ш
    и это должно работать как ожидалось.
  2. иметь такой код
    (Шебанг) 
    команда 1
     команда 2
     команда 3
    установить -x
    команда 4
     команда 5
     команда 6
    установить + х
    команда 7
     команда 8
     команда 9
    а также
    • Команды 4, 5 и 6 будут отслеживаться - если только одна из них не является echo, и в этом случае она будет выполнена, но не будет отслежена. (Но даже если команда 5 является echoкомандой, команда 6 все равно будет отслеживаться.)
    • Команды 7, 8 и 9 не будут отслеживаться. Даже если команда 8 является echoкомандой, команда 9 все равно не будет отслежена.
    • Команды 1, 2 и 3 будут отслеживаться (например, 4, 5 и 6) или нет (например, 7, 8 и 9) в зависимости от того, включает ли шебанг x.

PS Я обнаружил, что в моей системе я могу опустить builtinключевое слово в моем среднем ответе (то, для которого просто псевдоним echo). Это не удивительно; bash (1) говорит, что во время расширения псевдонима…

… Слово, идентичное раскрываемому псевдониму, не раскрывается во второй раз. Это означает , что один может псевдоним lsдля ls -F, например, и Баш не пытаться рекурсивно расширить текст замены.

Не удивительно, что последний ответ (тот, что с echo_and_restore) терпит неудачу, если builtinключевое слово опущено 1 . Но, как ни странно, это работает, если я удаляю builtinи переключаю порядок:

echo_and_restore() {
        echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'

__________
1  Похоже, это приводит к неопределенному поведению. я видел

  • бесконечный цикл (вероятно, из-за неограниченной рекурсии),
  • /dev/null: Bad addressсообщение об ошибке, и
  • основной дамп.
G-Man говорит: «Восстанови Монику»
источник
2
Я видел несколько удивительных магических трюков с псевдонимами, поэтому я знаю, что мои знания о них неполны. Если кто-нибудь может представить способ сделать эквивалент echo +x; echo "$*"; echo -xв псевдониме, я хотел бы увидеть это.
G-Man говорит: «Восстановите Монику»
11

Я нашел частичное решение в InformIT :

#!/bin/bash -ex
set +x; 
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

выходы

set +x; 
shell tracing is disabled here 
+ echo "but is enabled here"
but is enabled here

К сожалению, это все еще отголоски set +x, но по крайней мере после этого тихо. так что это как минимум частичное решение проблемы.

Но есть ли лучший способ сделать это? :)

Кристиан
источник
2

Этот способ улучшает ваше собственное решение, избавляясь от set +xвывода:

#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"
user5071535
источник
2

Поместите set +xв скобки, так что это будет применяться только для локальной области.

Например:

#!/bin/bash -x
exec 3<> /dev/null
(echo foo1 $(set +x)) 2>&3
($(set +x) echo foo2) 2>&3
( set +x; echo foo3 ) 2>&3
true

будет выводить:

$ ./foo.sh 
+ exec
foo1
foo2
foo3
+ true
kenorb
источник
Поправьте меня, если я ошибаюсь, но я не думаю, что set +xвнутренняя часть подоболочки (или вообще не использующая подоболочки) делает что-то полезное. Вы можете удалить его и получить тот же результат. Это перенаправление stderr в / dev / null, которое выполняет работу по временному «отключению» трассировки ... Кажется echo foo1 2>/dev/null, и т. Д. Было бы так же эффективно и более читабельно.
Тайлер Рик
Наличие трассировки в вашем скрипте может повлиять на производительность. Во-вторых, перенаправление & 2 на NULL может быть не таким, когда вы ожидаете некоторых других ошибок.
Кенорб
Поправьте меня, если я ошибаюсь, но в вашем примере у вас уже включена трассировка в вашем скрипте (с bash -x), и вы уже перенаправляете &2на ноль (так как &3был перенаправлен на ноль), поэтому я не уверен, насколько этот комментарий уместен. Может быть, нам просто нужен лучший пример, который иллюстрирует вашу точку зрения, но, по крайней мере, в данном примере все еще кажется, что его можно упростить, не теряя никакой выгоды.
Тайлер Рик
1

Мне нравится исчерпывающий и хорошо объясненный ответ от g-man , и я считаю его лучшим из представленных на данный момент. Он заботится о контексте скрипта и не вызывает конфигурации, когда они не нужны. Итак, если вы читаете этот ответ, сначала попробуйте и проверьте его, вся заслуга в этом.

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

COMMAND || echo "Command failed!"

Из-за того, как построен псевдоним, это расширится до

COMMAND || { save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore "Command failed!"

и как вы уже догадались, echo_and_restoreисполняется всегда , безоговорочно. Учитывая, что set +xчасть не выполнялась, это означает, что содержимое этой функции также будет напечатано.

Изменение последнего , ;чтобы &&не сработало, потому что в Bash, ||и &&являются левоассоциативными .

Я нашел модификацию, которая работает для этого варианта использования:

echo_and_restore() {
    echo "$(cat -)"
    case "$save_flags" in
        (*x*) set -x
    esac
}
alias echo='({ save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore) <<<'

Он использует подоболочку ( (...)часть), чтобы сгруппировать все команды, а затем передает входную строку через stdin как Here String ( <<<вещь), которая затем печатается cat -. Опция -необязательна, но вы знаете, « явное лучше, чем неявное ».

Вы также можете использовать cat -напрямую без echo , но мне нравится этот способ, потому что он позволяет мне добавлять любые другие строки в вывод. Например, я склонен использовать это так:

BASENAME="$(basename "$0")"  # Complete file name
...
echo "[${BASENAME}] $(cat -)"

И теперь это работает прекрасно:

false || echo "Command failed"
> [test.sh] Command failed
j1elo
источник
0

Трассировка выполнения идет к stderr, фильтруйте это следующим образом:

./script.sh 2> >(grep -v "^+ echo " >&2)

Некоторое объяснение, шаг за шагом:

  • stderr перенаправлен ... - 2>
  • ... в команду. ->(…)
  • grep это команда ...
  • ... который требует начала строки ... - ^
  • ... чтобы последовать + echo...
  • ... затем grepпереворачивает матч ... --v
  • ... и это отбрасывает все строки, которые вы не хотите.
  • Результат обычно идет к stdout; мы перенаправляем его туда, stderrгде он принадлежит. ->&2

Проблема в том (я думаю), что это решение может десинхронизировать потоки. Из-за фильтрации stderrможет быть немного поздно по отношению к stdout(где echoвывод принадлежит по умолчанию). Чтобы это исправить, вы можете сначала присоединиться к потокам, если не возражаете против их обоих stdout:

./script.sh > >(grep -v "^+ echo ") 2>&1

Вы можете встроить такую ​​фильтрацию в сам скрипт, но этот подход наверняка склонен к десинхронизации (то есть это происходило в моих тестах: след выполнения команды может появиться после вывода сразу следующего echo).

Код выглядит так:

#!/bin/bash -x

{
 # original script here
 # …
} 2> >(grep -v "^+ echo " >&2)

Запустите его без каких-либо хитростей:

./script.sh

Опять же, используйте > >(grep -v "^+ echo ") 2>&1для поддержания синхронизации за счет объединения потоков.


Другой подход. Вы получаете «немного избыточный» и странно выглядящий вывод, потому что ваш терминал смешивает stdoutиstderr . Эти два потока являются разными животными по причине. Проверьте, соответствует ли анализ stderrтолько вашим потребностям; отменить stdout:

./script.sh > /dev/null

Если в вашем скрипте есть echoсообщение об отладке / ошибке печати, stderrвы можете избавиться от избыточности способом, описанным выше. Полная команда:

./script.sh > /dev/null 2> >(grep -v "^+ echo " >&2)

На этот раз мы работаем stderrтолько с десинхронизацией, поэтому больше не проблема. К сожалению, таким образом, вы не увидите ни следа, ни вывода echoэтих отпечатков stdout(если есть). Мы могли бы попытаться перестроить наш фильтр, чтобы обнаружить перенаправление ( >&2), но если вы посмотрите echo foobar >&2, echo >&2 foobarи echo "foobar >&2"тогда вы, вероятно, согласитесь, что все усложняется.

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


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

./script.sh > /dev/null 2> >(grep "^+ " >&2)

Защищенное? Подумай, что будет, если echo "+ rm -rf --no-preserve-root /" >&2в сценарии есть. У кого-то может случиться сердечный приступ.


И наконец…

К счастью, есть BASH_XTRACEFDпеременная окружения. От man bash:

BASH_XTRACEFD
Если задано целое число, соответствующее допустимому дескриптору файла, bash запишет выходные данные трассировки, сгенерированные при set -xвключении этого дескриптора файла.

Мы можем использовать это так:

(exec 3>trace.txt; BASH_XTRACEFD=3 ./script.sh)
less trace.txt

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

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

Конечно, вы можете использовать другой метод, особенно когда вам нужно проанализировать stdoutи / или stderrвместе с вашими следами. Вам просто нужно помнить, что есть определенные ограничения и подводные камни. Я пытался показать (некоторые из них) их.

Камил Мачоровский
источник
0

В Makefile вы можете использовать @символ.

Пример использования: @echo 'message'

Из этого документа GNU:

Когда строка начинается с «@», эхо этой строки подавляется. '@' Отбрасывается перед передачей строки в оболочку. Обычно вы используете это для команды, чей единственный эффект - напечатать что-то, например команду echo для индикации прохождения через make-файл.

Derek
источник
Привет, документ, на который вы ссылаетесь, предназначен для gnu make, а не для shell. Вы уверены, что это работает? Я получаю ошибку ./test.sh: line 1: @echo: command not found, но я использую Bash.
Вау, извини, я совершенно не правильно понял вопрос. Да, @работает только когда вы повторяете в make-файлах.
Дерек
@derek Я отредактировал ваш оригинальный ответ, и теперь в нем четко указано, что решение ограничено только для Makefiles. Я действительно искал это, поэтому я хотел, чтобы ваш комментарий не имел негативную репутацию. Надеюсь, люди тоже найдут это полезным.
Шакарон
-1

Отредактировано 29 октября 2016 г. для модератора, предложения о том, что оригинал не содержал достаточно информации, чтобы понять, что происходит.

Этот «трюк» производит только одну строку вывода сообщения на терминале, когда активен xtrace:

Первоначальный вопрос: есть ли способ оставить -x включенным, но выводить только сообщение вместо двух строк ? Это точная цитата из вопроса.

Я понимаю вопрос, как "оставить set -x включенным И создать одну строку для сообщения"?

  • В глобальном смысле этот вопрос, в основном, касается эстетики - спрашивающий хочет создать одну строку вместо двух фактически дублированных строк, создаваемых при активной функции xtrace.

Итак, в итоге, ОП требует:

  1. Чтобы установить -x в силу
  2. Создать сообщение, удобочитаемое человеком
  3. Производить только одну строку вывода сообщения.

OP не требует использования команды echo . Они привели это в качестве примера создания сообщения, используя сокращение, например, которое означает латинский examplelitia или «например».

Думая «нестандартно» и отказываясь от использования echo для создания сообщений, я отмечаю, что оператор присваивания может удовлетворить все требования.

Сделайте присвоение текстовой строки (содержащей сообщение) неактивной переменной.

Добавление этой строки в скрипт не изменяет никакой логики, но выдает одну строку, когда трассировка активна: (Если вы используете переменную $ echo , просто измените имя на другую неиспользуемую переменную)

echo="====================== Divider line ================="

На терминале вы увидите только 1 строку:

++ echo='====================== Divider line ================='

не два, как не любит ОП:

+ echo '====================== Divider line ================='
====================== Divider line =================

Вот пример сценария для демонстрации. Обратите внимание, что подстановка переменных в сообщение (имя каталога $ HOME через 4 строки в конце) работает, поэтому вы можете отслеживать переменные, используя этот метод.

#!/bin/bash -exu
#
#  Example Script showing how, with trace active, messages can be produced
#  without producing two virtually duplicate line on the terminal as echo does.
#
dummy="====================== Entering Test Script ================="
if [[ $PWD == $HOME ]];  then
  dummy="*** "
  dummy="*** Working in home directory!"
  dummy="*** "
  ls -la *.c || :
else
  dummy="---- C Files in current directory"
  ls -la *.c || :
  dummy="----. C Files in Home directory "$HOME
  ls -la  $HOME/*.c || :
fi

И вот результат его запуска в корне, а затем в домашнем каталоге.

$ cd /&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ / == /g/GNU-GCC/home/user ]]
+ dummy='---- C Files in current directory'
+ ls -la '*.c'
ls: *.c: No such file or directory
+ :
+ dummy='----. C Files in Home directory /g/GNU-GCC/home/user'
+ ls -la /g/GNU-GCC/home/user/HelloWorld.c /g/GNU-GCC/home/user/hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/hw.c
+ dummy=---------------------------------

$ cd ~&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ /g/GNU-GCC/home/user == /g/GNU-GCC/home/user ]]
+ dummy='*** '
+ dummy='*** Working in home directory!'
+ dummy='*** '
+ ls -la HelloWorld.c hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 hw.c
+ dummy=---------------------------------
HiTechHiTouch
источник
1
Обратите внимание, что даже отредактированная версия вашего удаленного ответа (копия которого является его копией) по-прежнему не отвечает на вопрос, поскольку ответ не выводит только сообщение без связанной с ним команды echo, которая запрашивала OP.
DavidPostill
Обратите внимание, что этот ответ обсуждается в мета. Я был оштрафован за ответ 1, который не понравился модератору?
DavidPostill
@ Давид Я расширил объяснение, согласно комментариям в мета-форуме.
HiTechHiTouch
@ Давид Снова я призываю вас внимательно прочитать ОП, обращая внимание на латиницу ".eg". Постер НЕ требует использования команды echo. OP только ссылается на echo в качестве примера (наряду с перенаправлением) потенциальных методов для решения проблемы. Оказывается, вам тоже не нужно,
HiTechHiTouch
А что, если ОП хочет сделать что-то подобное echo "Here are the files in this directory:" *?
G-Man говорит: «Восстановите Монику»