Повторяет ли последняя команда, запущенная в Bash?

Я пытаюсь повторить последний запуск команды внутри bash script. Я нашел способ сделать это с помощью history,tail,head,sed, который отлично работает, когда команды представляют определенную строку в моем script с точки зрения парсера. Однако в некоторых случаях я не получаю ожидаемый результат, например, когда команда вставлена ​​внутри оператора case:

script:

#!/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 script?

Мой ответ

Несмотря на то, что мои коллеги из SO'ers высоко оценили меня, я решил написать функцию run, которая запускает все свои параметры как одну команду и отображает команду и код ее ошибки при ее отсутствии - со следующими преимуществами:
- Мне нужно только добавить команды, которые я хочу проверить, с помощью run, которая хранит их в одной строке и не влияет на кратность моего script
-Whenever script не работает с одной из этих команд, последняя выходная строка моего script - это сообщение, в котором четко отображается, какая команда выходит из строя вместе с кодом выхода, что облегчает отладку

Пример script:

#!/bin/bash
die() { echo >&2 -e "\nERROR: [email protected]\n"; exit 1; }
run() { "[email protected]"; 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 не нарушала их. Единственная проблема, которую я нашел до сих пор, - это запустить эхо, которое ломается, но я все равно не планирую проверять свои эхо.

Ответ 1

История команд - это интерактивная функция. Только полные команды вводятся в историю. Например, конструкция 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 чтобы ваш скрипт set -e с первой неудачной командой. Вы можете отобразить последнюю команду из ловушки EXIT.

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

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

Ответ 2

Bash имеет встроенные функции для доступа к последней выполненной команде. Но эта последняя целая команда (например, целая команда case), а не отдельные простые команды, как вы изначально запрашивали.

!:0= имя выполняемой команды.

!:1= первый параметр предыдущей команды

!:*= все параметры предыдущей команды

!:-1= конечный параметр предыдущей команды

!!= предыдущая командная строка

и др.

Итак, самый простой ответ на вопрос: фактически,

echo !!

... альтернативно:

echo "Last command run was ["!:0"] with arguments ["!:*"]"

Попробуйте сами!

echo this is a test
echo !!

В script расширение истории отключено по умолчанию, вам нужно включить его с помощью

set -o history -o histexpand

Ответ 3

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

Итак, следующий bash script работает так, как ожидалось:

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

Ответ 4

Я смог добиться этого, используя set -x в главном script (который делает script распечатывать каждую выполняемую команду) и записывает обертку script, которая показывает только последнюю строку вывода генерируется set -x.

Это основной script:

#!/bin/bash
set -x
echo some command here
echo last command

И это оболочка script:

#!/bin/sh
./test.sh 2>&1 | grep '^\+' | tail -n 1 | sed -e 's/^\+ //'

Запуск обертки script выводит это как вывод:

echo last command

Ответ 5

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

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

Ответ 6

Между последней командой ($ _) и последней переменной ошибки ($?) существует расы. Если вы попытаетесь сохранить один из них в собственной переменной, то оба из них столкнулись с новыми значениями уже из-за команды set. Фактически, последняя команда вообще не имеет значения в этом случае.

Вот что я сделал, чтобы хранить (почти) информацию в собственных переменных, поэтому мой bash script может определить, была ли какая-либо ошибка И установка заголовка с последней командой запуска:

   # 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

Этот script сохранит информацию, если произошла ошибка, и получите последнюю команду прогона. Из-за состояния расы я не могу сохранить фактическое значение. Кроме того, большинство команд на самом деле даже не заботятся о ошибках noumbers, они просто возвращают что-то отличное от "0". Вы заметите, что если вы используете расширение errono bash.

Это должно быть возможно с чем-то вроде "intern" script для 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

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