Придушити слід виконання команди echo?


29

Я запускаю сценарії оболонки від Дженкінса, яка розпочинає сценарії оболонки з параметрами 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. Але, якщо shebang є #!/bin/bash, вам потрібно додати shopt -s expand_aliasesкоманду, щоб отримати псевдоніми для роботи в сценарії.)

Отже, для мого першого фокусу:

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

Тепер, коли ми говоримо echo "Message", ми називаємо псевдонім, який не простежується. Псевдонім вимикає параметр сліду, пригнічуючи повідомлення про сліду з setкоманди (використовуючи методику, представлену спочатку у відповіді user5071535 ), а потім виконує фактичну 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- і можемо, з трохи більше хитрістю. Але перед тим, як це зробити, врахуйте це. OP починається зі сценаріїв, які використовують #!/bin/sh -exшебанг. Користувач неявно міг би видалити xз шебангу та створити сценарій, який працює нормально, без відстеження виконання. Було б добре, якби ми могли розробити рішення, яке зберігає цю властивість. Перші кілька відповідей тут не сприймають цю властивість, тому що вони без зволікання відслідковують "назад" після 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. використовувати будь-яку з наступних рядків shebang:
    #! / bin / sh -ex
    #! / бін / ш -е
    #! / бін / ш –х
    або просто простий
    #! / бін / ш
    і це має працювати, як очікувалося.
  2. мати код, як
    (shebang) 
    команда 1
     команда 2
     команда 3
    встановити -x
    команда 4
     команда 5
     команда 6
    встановити + x
    команда 7
     команда 8
     команда 9
    і
    • Команди 4, 5 і 6 будуть простежуватися - якщо тільки одна з них не є echo, в цьому випадку вона буде виконуватися, але не простежується. (Але навіть якщо команда 5 є an echo, команда 6 все одно буде простежена.)
    • Команди 7, 8 і 9 не простежуються. Навіть якщо команда 8 є an 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повідомлення про помилку та
  • ядро звалища.

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"

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

Виправте мене, якщо я помиляюся, але я не думаю, що set +xвнутрішня частина підкабелі (або взагалі використання підпакетів) не робить нічого корисного. Ви можете його видалити і отримати той же результат. Це перенаправлення stderr на / dev / null, що виконує роботу тимчасово "відключення" відстеження ... Здається echo foo1 2>/dev/null, і т.д., було б настільки ж ефективним і читабельнішим.
Тайлер Рік

Відстеження у вашому сценарії може вплинути на ефективність. По-друге, перенаправлення & 2 на NULL може бути не таким, коли ви очікуєте деяких інших помилок.
kenorb

Виправте мене, якщо я помиляюся, але у вашому прикладі у вас вже ввімкнено відстеження у вашому сценарії (з bash -x), і ви вже переспрямовуєте &2на null (оскільки &3було перенаправлено на null), тому я не впевнений, наскільки цей коментар доречний. Можливо, нам просто потрібен кращий приклад, який ілюструє вашу думку, але, принаймні, у цьому прикладі все ще здається, що його можна спростити, не втрачаючи жодної вигоди.
Тайлер Рік

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 ( <<<річ), яку потім надрукує 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)

Дурня? Ні. Подумайте, що буде, якщо є 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, щоб вказати прогрес через makefile.


Привіт, документ, на який ви посилаєтесь, призначений для gnu make, а не для оболонки. Ви впевнені, що це працює? Я отримую помилку ./test.sh: line 1: @echo: command not found, але я використовую bash.

Нічого собі, вибачте, що я повністю перечитав це питання. Так, @працює лише тоді, коли ви перегукуєтесь у грифах.
дерек

@derek Я відредагував вашу оригінальну відповідь, щоб тепер чітко сказано, що рішення обмежене лише для Makefiles. Я насправді шукав цього, тому хотів, щоб ваш коментар не мав негативної репутації. Сподіваємось, люди також знайдуть це корисним.
Шакарон

-1

Відредаговано 29 жовтня 2016 р. На думку модератора, що в оригіналі не було достатньо інформації, щоб зрозуміти, що відбувається.

Цей "трюк" створює лише один рядок виводу повідомлення в терміналі, коли xtrace активний:

Оригінальне запитання: чи є спосіб залишити -x увімкненим, але вивести лише повідомлення замість двох рядків . Це точна цитата з питання.

Я розумію питання, як "залишити набір -x увімкненим І створити єдиний рядок для повідомлення"?

  • У глобальному розумінні це питання в основному стосується естетики - запитуючий хоче створити єдиний рядок замість двох практично повторюваних ліній, що утворюються під час активності xtrace.

Отже, підсумовуючи, ОП вимагає:

  1. Мати встановлену -x дію
  2. Створіть повідомлення, зрозуміле для людини
  3. Створюйте лише один рядок виводу повідомлення.

OP не вимагає використання команди echo . Вони наводили це як приклад створення повідомлень, використовуючи абревіатуру, наприклад, яка означає латинську приклад gratia, або "наприклад".

Думаючи "поза коробкою" та відмовляючись від використання ехо для створення повідомлень, зауважу, що заява про призначення може виконати всі вимоги.

Виконайте призначення текстового рядка (містить повідомлення) неактивній змінній.

Додавання цього рядка до сценарію не змінює жодної логіки, але створює єдиний рядок під час активного відстеження: (Якщо ви використовуєте змінну $ echo , просто змініть ім'я на іншу невикористану змінну)

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

Що ви побачите на терміналі - це лише 1 рядок:

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

не два, оскільки ОП не любить:

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

Ось зразок сценарію для демонстрації. Зауважте, що підміна змінної в повідомленні (4 кінці рядка з іменем каталогу HOME) працює, тому ви можете відстежувати змінні за допомогою цього методу.

#!/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=---------------------------------

1
Зауважте, навіть відредагована версія вашої видаленої відповіді (яка є копією) все ще не відповідає на запитання, оскільки відповідь не виводить лише повідомлення без відповідної команди ехо, про що вимагав ОП.
DavidPostill

Зауважте, ця відповідь обговорюється мета: Чи мене покарали за відповідь, 1 модератору не сподобалось?
DavidPostill

@David Я розширив пояснення, за коментарями на метафорі.
HiTechHiTouch

@David Знову закликаю вас дуже уважно читати ОП, звертаючи увагу на латинську ".eg". Плакат НЕ вимагає використання команди ехо. ОП посилається лише на приклад (поряд із перенаправленням) потенційних методів вирішення проблеми. Виявляється, вам теж не потрібно,
HiTechHiTouch

А що, якщо ОП хоче зробити щось подібне echo "Here are the files in this directory:" *?
G-Man каже: "Відновіть Моніку"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.