23

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

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

echo "Message"

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

+ echo "Message"
Message

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

Message

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

8 ответов8

15

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

Вопрос об 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 shebang.  Неявно пользователь может удалить 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
    или просто
    #!/ Бен / ш
    и это должно работать как ожидалось.
  2. иметь такой код
    (shebang) команда 1 команда 2 команда 3 команда set -x 4 команда 5 команда 6 set +x команда 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, и bash не пытается рекурсивно развернуть текст замены.

Неудивительно, что последний ответ (ответ с 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 сообщение об ошибке неверного адреса и
  • основной дамп.
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"
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
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 не была запущена, это означает, что содержимое этой функции также будет напечатано.

Смена последнего ; to && тоже не сработает, потому что в 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 (вещь <<< ), которая затем печатается с помощью cat - . - является обязательным, но вы знаете, « явный лучше, чем неявный ».

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

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

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

false || echo "Command failed"
> [test.sh] Command failed
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)

Foolproof? Нет. Подумайте, что произойдет, если в скрипте есть 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-файл.

-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=---------------------------------

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .