Я пытаюсь повторить последнюю команду, запущенную внутри сценария bash. Я нашел способ сделать это с некоторыми, history,tail,head,sed
которые отлично работают, когда команды представляют определенную строку в моем сценарии с точки зрения парсера. Однако при некоторых обстоятельствах я не получаю ожидаемого результата, например, когда команда вставлена внутри case
оператора:
Сценарий:
#!/bin/bash
set -o history
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"
case "1" in
"1")
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"
;;
esac
Выход:
Tue May 24 12:36:04 CEST 2011
last command is [date]
Tue May 24 12:36:04 CEST 2011
last command is [echo "last command is [$last]"]
[Q] Может ли кто-нибудь помочь мне найти способ повторить последнюю команду запуска независимо от того, как / где эта команда вызывается в сценарии bash?
Мой ответ
Несмотря на очень ценный вклад от моих товарищей по SO, я решил написать run
функцию, которая запускает все свои параметры как одну команду и отображает команду и ее код ошибки в случае сбоя со следующими преимуществами:
-Мне нужно только добавьте команды, которые я хочу проверить, с помощью run
которых они хранятся в одной строке и не влияют на краткость моего скрипта -
всякий раз, когда скрипт не выполняет одну из этих команд, последняя строка вывода моего скрипта представляет собой сообщение, которое четко показывает, какая команда дает сбой вместе с кодом выхода, что упрощает отладку
Пример сценария:
#!/bin/bash
die() { echo >&2 -e "\nERROR: $@\n"; exit 1; }
run() { "$@"; code=$?; [ $code -ne 0 ] && die "command [$*] failed with error code $code"; }
case "1" in
"1")
run ls /opt
run ls /wrong-dir
;;
esac
Выход:
$ ./test.sh
apacheds google iptables
ls: cannot access /wrong-dir: No such file or directory
ERROR: command [ls /wrong-dir] failed with error code 2
Я тестировал различные команды с несколькими аргументами, переменными bash в качестве аргументов, аргументами в кавычках ... и run
функция их не сломала. Единственная проблема, которую я обнаружил до сих пор, - это запуск эха, которое прерывается, но я все равно не планирую проверять свои эхо.
run()
не работает должным образом, когда используются кавычки, например , это не удается:run ssh-keygen -t rsa -C info@example.org -f ./id_rsa -N ""
."something"
аргументы с помощью'"something"'
(или, скорее,"'something'"
чтобы разрешитьsomething
(например: переменные) интерпретировать / оценивать на первом уровне, если необходимо)run() { $*; … }
на более близкий к правильному,run() { "$@"; … }
потому что ошибочный ответ привел к тому, что вопрос завершилсяcp
со статусом ошибки 64 , где проблема заключалась в том,$*
что аргументы команды сломались в пробелах в именах, но"$@"
не сделали этого.last=$(history | tail -n1 | sed 's/^[[:space:]][0-9]*[[:space:]]*//g')
работал лучше, по крайней мере, для zsh и macOS 10.11Ответы:
История команд - это интерактивная функция. В историю заносятся только полные команды. Например,
case
конструкция вводится как единое целое, когда оболочка завершает ее анализ. Ни поиск истории с помощьюhistory
встроенной программы (ни ее печать с помощью расширения оболочки (!:p
)) не делает того, что вам кажется, а именно печати вызовов простых команд.DEBUG
Ловушка позволяет выполнить команду прямо перед любым простым выполнением команды. ВBASH_COMMAND
переменной доступна строковая версия команды для выполнения (со словами, разделенными пробелами) .trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG … echo "last command is $previous_command"
Обратите внимание, что это
previous_command
будет меняться каждый раз, когда вы запускаете команду, поэтому сохраните ее в переменной, чтобы использовать. Если вы хотите узнать статус возврата предыдущей команды, сохраните их в одной команде.cmd=$previous_command ret=$? if [ $ret -ne 0 ]; then echo "$cmd failed with error code $ret"; fi
Более того, если вы хотите прервать выполнение только невыполненных команд, используйте,
set -e
чтобы завершить выполнение сценария при первой неудачной команде. Вы можете отобразить последнюю команду изEXIT
ловушки .set -e trap 'echo "exit $? due to $previous_command"' EXIT
Обратите внимание: если вы пытаетесь отследить свой сценарий, чтобы увидеть, что он делает, забудьте обо всем этом и используйте
set -x
.источник
-x
выводит каждую команду, но, к сожалению, меня интересуют только те команды, которые терпят неудачу (чего я могу добиться с помощью своей команды, если помещу ее в[ ! "$? == "0" ]
оператор).set -e
(часто, но не всегда, команда выдаст достаточно хорошее сообщение об ошибке, поэтому вам не нужно предоставлять дополнительный контекст).eval echo "${BASH_COMMAND}"
может выполнять произвольный код в подстановках команд. Это опасно. Рассмотрим команду вродеcd $(ls -td | head -n 1)
- а теперь представьте, что вызывается подстановка командыrm
или что-то в этом роде.Bash имеет встроенные функции для доступа к последней выполненной команде. Но это последняя вся команда (например, вся
case
команда), а не отдельные простые команды, как вы изначально запрашивали.!:0
= имя выполненной команды.!:1
= первый параметр предыдущей команды!:*
= все параметры предыдущей команды!:-1
= последний параметр предыдущей команды!!
= предыдущая командная строкаи т.п.
Итак, самый простой ответ на вопрос:
echo !!
... альтернативно:
echo "Last command run was ["!:0"] with arguments ["!:*"]"
Попробуй сам!
echo this is a test echo !!
В скрипте раскрытие истории отключено по умолчанию, вам нужно включить его с помощью
set -o history -o histexpand
источник
sudo !!
set -o history -o histexpand; echo "!!"
сценарии bash я все еще получаю сообщение об ошибке:!!: event not found
( То же самое, без кавычек.)set -o history -o histexpand
в скриптах -> спасатель! благодаря!bash
продолжает печатать !! вместо последней команды запуска. Где это задокументировано?После прочтения ответа от Жиля , я решил посмотреть , если
$BASH_COMMAND
переменная была также доступна (и требуемое значение) вEXIT
ловушку - и это!Итак, следующий сценарий bash работает должным образом:
#!/bin/bash exit_trap () { local lc="$BASH_COMMAND" rc=$? echo "Command [$lc] exited with code [$rc]" } trap exit_trap EXIT set -e echo "foo" false 12345 echo "bar"
На выходе
foo Command [false 12345] exited with code [1]
bar
никогда не печатается, потому чтоset -e
заставляет bash выйти из сценария, когда команда терпит неудачу, а команда false всегда терпит неудачу (по определению).12345
Передаютсяfalse
только там , чтобы показать , что аргументы несостоявшихся команд захватываются , а также (false
команда игнорирует все аргументы , переданные ему)источник
Я смог добиться этого, используя
set -x
в основном скрипте (который заставляет скрипт распечатывать каждую выполняемую команду) и писать скрипт-оболочку, который просто показывает последнюю строку вывода, сгенерированнуюset -x
.Это основной сценарий:
#!/bin/bash set -x echo some command here echo last command
А это сценарий-обертка:
#!/bin/sh ./test.sh 2>&1 | grep '^\+' | tail -n 1 | sed -e 's/^\+ //'
Запуск сценария-оболочки дает следующий результат:
echo last command
источник
history | tail -2 | head -1 | cut -c8-999
tail -2
возвращает две последние командные строки из историиhead -1
возвращает только первую строку,cut -c8-999
возвращает только командную строку, удаляя PID и пробелы.источник
Между переменной последней команды ($ _) и последней ошибки ($?) Существует условие гонки. Если вы попытаетесь сохранить одно из них в собственной переменной, оба уже встретили новые значения из-за команды set. Собственно, последняя команда в данном случае вообще не имеет значения.
Вот что я сделал, чтобы сохранить (почти) обе информации в собственных переменных, поэтому мой сценарий bash может определить, была ли ошибка, И установить заголовок с помощью последней команды запуска:
# This construct is needed, because of a racecondition when trying to obtain # both of last command and error. With this the information of last error is # implied by the corresponding case while command is retrieved. if [[ "${?}" == 0 && "${_}" != "" ]] ; then # Last command MUST be retrieved first. LASTCOMMAND="${_}" ; RETURNSTATUS='✓' ; elif [[ "${?}" == 0 && "${_}" == "" ]] ; then LASTCOMMAND='unknown' ; RETURNSTATUS='✓' ; elif [[ "${?}" != 0 && "${_}" != "" ]] ; then # Last command MUST be retrieved first. LASTCOMMAND="${_}" ; RETURNSTATUS='✗' ; # Fixme: "$?" not changing state until command executed. elif [[ "${?}" != 0 && "${_}" == "" ]] ; then LASTCOMMAND='unknown' ; RETURNSTATUS='✗' ; # Fixme: "$?" not changing state until command executed. fi
Этот сценарий сохранит информацию, если произошла ошибка, и получит последнюю команду запуска. Из-за состояния гонки я не могу сохранить фактическое значение. Кроме того, большинству команд даже не нужны номера ошибок, они просто возвращают что-то отличное от «0». Вы заметите это, если используете расширение bash.
Это должно быть возможно с чем-то вроде "внутреннего" сценария для bash, например, в расширении bash, но я не знаком с чем-то подобным, и это также не будет совместимо.
ИСПРАВЛЕНИЕ
Я не думал, что можно получить обе переменные одновременно. Хотя мне нравится стиль кода, я предполагал, что он будет интерпретирован как две команды. Это было неправильно, поэтому мой ответ сводится к следующему:
# Because of a racecondition, both MUST be retrieved at the same time. declare RETURNSTATUS="${?}" LASTCOMMAND="${_}" ; if [[ "${RETURNSTATUS}" == 0 ]] ; then declare RETURNSYMBOL='✓' ; else declare RETURNSYMBOL='✗' ; fi
Хотя мой пост мог и не получить положительной оценки, я, наконец, решил свою проблему сам. И это кажется уместным в отношении исходного поста. :)
источник