Повторяет ли последняя команда, запущенная в Bash? - PullRequest
71 голосов
/ 24 мая 2011

Я пытаюсь повторить последний запуск команды внутри скрипта 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'ers, я решил написать функцию 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 в качестве аргументов, аргументы в кавычках ... и *Функция 1031 * не сломала их.Единственная проблема, которую я нашел до сих пор, - это запустить эхо, которое прерывается, но я все равно не планирую проверять свое эхо.

Ответы [ 6 ]

146 голосов
/ 29 февраля 2012

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
54 голосов
/ 24 мая 2011

История команд является интерактивной функцией.Только полные команды вводятся в историю.Например, конструкция case вводится целиком, когда оболочка закончила ее анализ.Ни просмотр истории с помощью встроенного history (ни печать ее через расширение оболочки (!:p)) не делает то, что вы, кажется, хотите, то есть печатать вызовы простых команд.

The DEBUG trap позволяет выполнить команду непосредственно перед выполнением любой простой команды.Строковая версия команды для выполнения (со словами, разделенными пробелами) доступна в переменной 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 trap .

set -e
trap 'echo "exit $? due to $previous_command"' EXIT

Обратите внимание, что если вы пытаетесь отследить свой скрипт, чтобы увидеть, что он делает, забудьте все это и используйтеset -x.

14 голосов
/ 01 апреля 2013

Прочитав ответ из Жиля , я решил посмотреть, доступен ли (и желаемое ли) значение $BASH_COMMAND в ловушке EXIT - и этоis!

Итак, следующий скрипт 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 для выхода из сценария в случае сбоя команды и всегда ложной команды (по определению).12345, переданный false, служит для того, чтобы показать, что аргументы неудачной команды также перехвачены (команда false игнорирует любые переданные ей аргументы)

7 голосов
/ 24 мая 2011

Я смог добиться этого, используя 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
1 голос
/ 17 мая 2019

history | tail -2 | head -1 | cut -c8-999

tail -2 возвращает последние две командные строки из истории head -1 возвращает только первую строку cut -c8-999 возвращает только командную строку, удаляя PID и пробелы.

0 голосов
/ 17 июля 2016

Существует условие гонки между переменными последней команды ($ _) и последней ошибки ($?). Если вы попытаетесь сохранить один из них в своей собственной переменной, оба обнаружат новые значения уже из-за команды 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'. Вы заметите это, если используете errono расширение bash.

Это должно быть возможно с чем-то вроде "intern" скрипта для bash, например, в расширении bash, но я не знаком с чем-то подобным, и это также не будет совместимо.

CORRECTION

Я не думал, что можно было извлечь обе переменные одновременно. Хотя мне нравится стиль кода, я предположил, что он будет интерпретироваться как две команды. Это было неправильно, поэтому мой ответ сводится к:

   # 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

Хотя мой пост не получил положительного рейтинга, я, наконец, решил свою проблему сам. И это кажется уместным в отношении первого поста. :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...