Bash에서 마지막 명령 실행을 반향합니까?


84

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하나의 명령으로 모든 매개 변수를 실행하고 명령과 그 실패의 오류 코드를 표시 - - 기능 다음과 같은 혜택을 :
에 -I에만 필요 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했는데 함수가이를 깨뜨리지 않았습니다. 지금까지 찾은 유일한 문제는 깨지는 에코를 실행하는 것이지만 어쨌든 에코를 확인할 계획은 없습니다.


+1, 멋진 아이디어! 그러나 run()따옴표를 사용하면 제대로 작동하지 않습니다 run ssh-keygen -t rsa -C info@example.org -f ./id_rsa -N "". 예를 들어 실패합니다 .
johndodo

단지 변화 :이 해결 될 수있다 : @johndodo "something"에 인수를 '"something"'(오히려, 또는 "'something'", 수 있도록 something: 변수) 필요한 경우, 첫 번째 수준에서 평가 / 해석하는 예 ()
올리비에 Dulac

2
잘못된 대답은 결국 64 오류 상태로 질문이 종료 되었기 때문에 오류 run() { $*; … }를 더 거의 올바른 것으로 변경했습니다 . 문제는 이름의 공백에서 명령 인수를 깨뜨 렸지만 그렇게하지 않을 것이라는 것입니다. run() { "$@"; … }cp$*"$@"
Jonathan Leffler 2014 년

유닉스 StackExchange에 관련 질문 : unix.stackexchange.com/questions/21930/...
haridsv

last=$(history | tail -n1 | sed 's/^[[:space:]][0-9]*[[:space:]]*//g')적어도 zsh 및 macOS 10.11에서 더 잘 작동했습니다.
phil pirozhkov

답변:


60

명령 기록은 대화 형 기능입니다. 기록에는 완전한 명령 만 입력됩니다. 예를 들어, 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.


1
DEBUG 트랩을 시도했지만 작동하지 않습니다. 전체 예제를 제공해 주시겠습니까? -x모든 명령을 출력하지만 안타깝게도 실패한 명령 만보고 싶습니다 ( [ ! "$? == "0" ]명령문 안에
Max

@ user359650 : 수정되었습니다. 현재 명령으로 덮어 쓰기 전에 이전 명령을 저장해야합니다. 명령이 실패한 경우 스크립트를 중단하려면 set -e(종종 항상 그런 것은 아니지만 명령이 충분한 오류 메시지를 생성하므로 추가 컨텍스트를 제공 할 필요가 없음)을 사용하십시오.
Gilles 'SO- 그만 사악함'

귀하의 기여에 감사드립니다. 솔루션이 너무 많은 오버 헤드로 인해 사용자 지정 함수 (내 게시물 참조)를 작성했습니다.
Max

놀라운 속임수. 확실한 +1. 나는 set -e 부분과 ERR 트랩을 가지고 있었고, 당신은 나에게 DEBUG 부분을 주었다. 감사합니다!
Philippe A.

1
@ JamesThomasMoon1979 일반적으로 eval echo "${BASH_COMMAND}"명령 대체에서 임의의 코드를 실행할 수 있습니다. 이건 위험 해. 다음과 같은 명령을 고려해보십시오. cd $(ls -td | head -n 1)이제 명령 대체라고하는 것을 상상해보십시오 rm.
Gilles 'SO- 사악한 중지

170

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

7
내가 본 가장 유용한 사용 사례는 sudo 액세스로 마지막 명령다시 실행하는 것입니다 . 즉sudo !!
Travesty3

1
set -o history -o histexpand; echo "!!"떠들썩한 파티 스크립트 난 여전히 오류 메시지가 : !!: event not found(그것은 인용 부호없이 동일합니다.)
Suzana

2
set -o history -o histexpand스크립트-> 생명의 은인! 감사!
Alberto Megía 2015

이 동작을 시간 함수에서 사용하는 TIMEFORMAT 문자열로 가져올 수있는 방법이 있습니까? ie export TIMEFORMAT = "***! : 0이 % 0lR을 사용했습니다"; / usr / bin / time find -name "* .log"... 내보내기를 실행할 때! : 0이 평가되기 때문에 작동하지 않습니다. (
Martin

set -o history -o histexpand 사용에 대해 자세히 읽어야합니다. 내가 호출하는 파일에서 사용bash 계속 인쇄됩니다 !! 마지막 실행 명령 대신. 이 문서는 어디에 있습니까?
Muno

17

Gilles답변 을 읽은 후 트랩 에서 var도 사용할 수 있는지 (및 원하는 값) 확인하기로 결정했습니다 .$BASH_COMMANDEXIT

따라서 다음 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]

barset -e명령이 실패하고 false 명령이 항상 실패 할 때 bash가 스크립트를 종료하므로 (정의에 따라) 인쇄되지 않습니다 . 그만큼12345에 전달 false실패한 명령에 대한 인수가 아니라 캡처 것을 보여주기 위해 단지가합니다 ( false명령은 전달 인수를 무시)


이것은 절대적으로 최상의 솔루션입니다. "set -euo pipefail"로 나를위한 매력처럼 작동
Vukasin

8

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

3

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

tail -2기록에서 마지막 두 명령 줄을 head -1반환합니다. 첫 번째 줄만 cut -c8-999반환합니다. 명령 줄 만 반환하고 PID와 공백을 제거합니다.


1
명령 인수가 무엇인지 설명해 주시겠습니까? 당신이 한 일을 이해하는 데 도움이 될 것입니다
Sigrist

이 질문에 대한 답변이 될 수 있지만이 답변이 문제 해결에 어떻게 도움이 될 수 있는지에 대한 설명을 추가하는 것이 좋습니다. 자세한 내용 을 알아 보려면 좋은 답변작성하는 방법을 읽어보십시오 .
Roshana Pitigala

1

마지막 명령 ($ _)과 마지막 오류 ($?) 변수 사이에 경쟁 조건이 있습니다. 그들 중 하나를 자신의 변수에 저장하려고하면 둘 다 이미 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의 errono 확장을 사용하면 알 수 있습니다.

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

내 게시물이 긍정적 인 평가를받지 못할 수도 있지만 마침내 내 문제를 직접 해결했습니다. 그리고 이것은 초기 포스트와 관련하여 적절 해 보입니다. :)


1
오, 이런, 한 번에 받기만하면 가능할 것 같습니다. declare RETURNSTATUS = "$ {?}"LASTCOMMAND = "$ {_}";
WGRM

한 가지 예외로 훌륭하게 작동합니다. 추가 매개 변수에 대한 별칭이 있으면 매개 변수 만 표시됩니다. 결론은 없나요?
WGRM
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.