캐치 함수 $LINENO 오류

캐치 함수 $LINENO 오류

저는 스크립팅을 배우기 위해 Bash 스크립트를 작성하고 있습니다. 어떤 시점에서는 스크립트가 종료될 때 불필요한 디렉터리와 파일이 정리되도록 트랩을 추가해야 합니다. 그러나 어떤 이유로 clean_a()스크립트가 종료되면 트랩이 정리 함수를 호출하지만 스크립트가 종료될 때 $LINENOint 함수가 아닌 정리 함수 자체의 행을 가리킨다는 점을 이해하지 못합니다.archieve_it()

예상되는 동작:

  1. 스크립트 실행
  2. Ctrl+를 누르세요C
  3. 트랩 캐시 Ctrl+ C호출 clean_a()기능
  4. clean_a()Ctrl이 함수는 +를 누른 행 번호를 에코합니다 C. 10번째 줄이 되도록 하세요 archieve_it().

실제로 일어난 일:

  1. 스크립트 실행
  2. Ctrl+를 누르세요C
  3. 트랩 캐시 Ctrl+ C호출 clean_a()기능
  4. clean_a()관련 없는 줄 번호를 에코합니다. 예를 들어 clean_a()함수의 25행입니다.

다음은 내 스크립트의 일부 예입니다.

archieve_it () {
  trap 'clean_a $LINENO $BASH_COMMAND'\
                SIGHUP SIGINT SIGTERM SIGQUIT
  for src in ${sources}; do
   mkdir -p "${dest}${today}${src}"
   if [[ "$?" -ne 0 ]] ; then
    error "Something!" 
   fi
   rsync "${options}" \
         --stats -i \
         --log-file="${dest}${rsync_log}" \
         "${excludes}" "${src}" "${dest}${today}${src}"
  done
}
clean_a () {
  error "something!
  line: $LINENO
  command: $BASH_COMMAND
  removing ${dest}${today}..."
  cd "${dest}"
  rm -rdf "${today}"
  exit "$1"
}

추신: 원본 스크립트가 표시됩니다.여기. 정의와 변수 이름은 터키어로 되어 있습니다. 필요한 경우 무엇이든 영어로 번역할 수 있습니다.

편집하다:@mikeserv의 설명을 바탕으로 스크립트를 최대한 변경했습니다. 다음과 같습니다.

#!/bin/bash
PS4='DEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO $BASH_COMMAND'\
                SIGHUP SIGINT SIGTERM SIGQUIT
  ..
}
clean_a () {
  error " ...
  line: $LINENO $LASTNO
  ..."
}

이제 스크립트를 실행하고 set -x+로 종료하면 다음과 같이 올바른 줄 번호가 인쇄됩니다.CtrlC

 DDEBUG: 1 : clean_a 1 336 rsync '"${options}"' ...

그러나 clean_a()함수에서는 값이 $LASTNO1로 인쇄됩니다.

 line: 462 1

@Arkadiusz Drabczyk가 표시한 오류와 관련이 있나요?

편집 2: @mikesrv 추천대로 스크립트를 변경했습니다. 그러나 스크립트가 종료되면 $LASTNO는 행 값으로 1을 반환합니다(337이어야 함).

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
                SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
clean_a () {
  error " ...
  line: $LASTNO $LINENO
  ..."
} 2>&1

스크립트를 실행하고 실행 중에 +를 사용하여 rsync를 종료하면 Ctrl다음과 같은 출력이 표시됩니다.C

^^MDEBUG: 1 : clean_a '337 1 rsync "${options}" --delete-during ...
...
line: 1 465

보시다시피 $LASTNO의 값은 1입니다.

문제가 무엇인지 알아내려고 할 때 testing매개변수 대체 형식을 사용하는 또 다른 함수를 작성했습니다 ${parameter:-default}. 따라서 스크립트는 다음과 같습니다.

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'testing "$LASTNO $LINENO $BASH_COMMAND"'\
                 SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
testing() {
  echo -e "${1:-Unknown error!}"
  exit 1
} 2>&1

이제 스크립트를 실행하고 Ctrl+를 누르면 C다음과 같은 결과가 나타납니다.

^^MDEBUG: 1 : testing '337 1 rsync "${options}" --delete-during ...
337 1 rsync "${options}" --delete-during ... 

337은 rsync가 실행되는 동안 Ctrl+를 누른 줄을 가리킵니다.C

clear_a또 다른 테스트를 위해 다음과 같은 함수를 작성해 보았습니다 .

clear_a () {
  echo -e " $LASTNO $LINENO"
}

그리고 $LASTNO는 여전히 1을 반환합니다.

그러면 매개변수 대체를 사용하면 스크립트가 종료될 때 올바른 줄 번호를 얻을 수 있다는 뜻인가요?

편집 3EDIT2에서 @mikeserv의 설명을 잘못 적용한 것 같습니다. 내 실수를 바로잡았습니다. 함수의 위치 매개변수는 "$1$LASTNO로 대체되어야 합니다.clear_a

내가 원하는 방식으로 작동하는 스크립트는 다음과 같습니다.

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
  trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
                SIGHUP SIGINT SIGTERM SIGQUIT
  ...
} 2>/dev/null
clean_a () {
  error " ...
  line: $1
  ..."
} 2>&1

스크립트가 종료되면 - 첫 번째 매개변수 -, - 두 번째 매개변수 - 및 - 세 번째 매개변수 - 가 trap평가되고 해당 값이 함수에 전달됩니다 . 마지막으로 스크립트가 종료되는 줄 번호로 $LASTNO를 인쇄합니다 .$LASTNO$LINENO$BASH_COMMANDclear_a$1

답변1

mikeserv의 솔루션은 좋지만 트랩을 실행하는 동안 fn해당 라인이 전달된다는 그의 설명은 trap올바르지 않습니다. $LINENO앞에 줄을 삽입하면 트랩이 선언된 위치에 관계없이 트랩이 실제로 항상 전달되는 trap ...것을 볼 수 있습니다 .fn1

PS4='DEBUG: $LINENO : ' \
bash -x <<\EOF
    echo Foo
    trap 'fn "$LINENO"' EXIT             
    fn() { printf %s\\n "$LINENO" "$1"; }
    echo "$LINENO"
    exit
EOF

산출

DEBUG: 1 : echo Foo
Foo
DEBUG: 2 : trap 'fn "$LINENO"' EXIT
DEBUG: 4 : echo 4
4
DEBUG: 5 : exit
DEBUG: 1 : fn 1
DEBUG: 3 : printf '%s\n' 3 1
3
1

트랩의 첫 번째 매개변수 fn "$LINENO"가 배치되므로하나의견적하다, $LINENO얻다확장하다,만약에 그리고 만약에EXIT를 트리거하므로 확장해야 합니다 fn 5. 그렇다면 왜 안 될까요? 실제로 bash-4.0까지는 트랩이 트리거될 때 $LINENO가 1로 재설정되어 fn 1.[원천]그러나 ERR 트랩의 원래 동작은 여전히 ​​유지됩니다. 아마도 비슷한 것이 trap 'echo "Error at line $LINENO"' ERR너무 자주 사용되기 때문일 것입니다.

#!/bin/bash

trap 'echo "exit at line $LINENO"' EXIT
trap 'echo "error at line $LINENO"' ERR
false
exit 0

산출

error at line 5
exit at line 1

GNU bash 버전 4.3.42(1) 릴리스(x86_64-pc-linux-gnu)를 사용하여 테스트되었습니다.

답변2

내 생각에 문제는 "$LINENO"거의 작동할 수도 있는 마지막 명령의 실행 라인을 제공할 것으로 기대하고 있다는 것입니다.clean_a() 반품자신의 것을 가지려면 $LINENO다음과 같이 해야 합니다:

error "something!
line: $1
...

하지만 설정한 내용만 인쇄하기를 원하므로 이 방법도 작동하지 않을 수 있습니다 trap.

다음은 작은 데모입니다.

PS4='DEBUG: $LINENO : ' \
bash -x <<\CMD          
    trap 'fn "$LINENO"' EXIT             
    fn() { printf %s\\n "$LINENO" "$1"; }
    echo "$LINENO"
CMD

산출

DEBUG: 1 : trap 'fn "$LINENO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1
DEBUG: 2 : printf '%s\n' 2 1
2
1

따라서 trap설정을 얻은 다음 fn()정의하고 echo실행하십시오. 쉘이 입력 실행을 마치면 EXIT트랩이 실행되고 fn호출됩니다. 하나의 인수가 전달됩니다. 이는 해당 trap행의 입니다 $LINENO. fn먼저 자체 내용을 인쇄한 $LINENO다음 첫 번째 인수를 인쇄합니다.

예상한 동작을 얻을 수 있는 방법을 생각할 수 있지만 이는 쉘을 엉망으로 만듭니다 stderr.

PS4='DEBUG: $((LASTNO=$LINENO)) : ' \
bash -x <<\CMD
    trap 'fn "$LINENO" "$LASTNO"' EXIT
    fn() { printf %s\\n "$LINENO" "$LASTNO" "$@"; }
    echo "$LINENO"
CMD

산출

DEBUG: 1 : trap 'fn "$LINENO" "$LASTNO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1 3
DEBUG: 2 : printf '%s\n' 2 1 1 3
2
1
1
3

이는 쉘의 디버그 프롬프트를 사용하여 각 실행 행을 $PS4정의합니다 . $LASTNO이는 현재 쉘 변수이며 스크립트의 어느 곳에서나 액세스할 수 있습니다. 즉, 현재 액세스 중인 행에 관계없이 실행 중인 스크립트의 최신 행을 참조할 수 있습니다 $LASTNO. 물론 보시다시피 디버그 출력이 함께 제공됩니다. 이를 2>/dev/null스크립트 실행의 대부분 으로 푸시 한 다음 다른 작업을 2>&1수행 할 수 있습니다 clean_a().

1네가 들어온 이유 $LASTNO마지막 값이 설정되었습니다. $LASTNO왜냐하면 그것이 마지막 $LINENO값이기 때문입니다. 아래 사양에 설명된 대로 function trap에 귀하의 기능이 있으므로 자체 archieve_it()기능도 있습니다 . 어쨌든 올바른 일을 하고 있는 $LINENO것처럼 보이지는 않지만 신호에 따라 셸을 다시 실행해야 하므로 재설정될 수도 있습니다. 이 경우에 나는 그것에 대해 약간 모호합니다. 분명히 그게 전부입니다.bashtrapINT$LINENObash

$LASTNO내 생각 엔 당신이 평가를 하고 싶지 않을 것 같아요 clean_a(). 더 나은 접근 방식은 이를 평가하고 수신된 trap값을 매개변수로 에 전달하는 것입니다. 어쩌면 다음과 같은 것일 수도 있습니다.trap$LASTNOclean_a()

#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
    trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
        SIGHUP SIGINT SIGTERM SIGQUIT
    while :; do sleep 1; done
} 2>/dev/null
clean_a () { : "$@" ; } 2>&1

한번 시도해 보세요. 원하는 대로 작동해야 한다고 생각합니다. 아 - Ctrl+V Enter와 마찬가지로 문자 그대로의 반환이 PS4=^M있다는 점에 유의하세요.^M

~에서POSIX 쉘 사양:

각 명령이 실행되기 전에 쉘에서 스크립트나 함수의 현재 연속 행 번호를 나타내는 10진수로 설정합니다(번호는 1부터 시작). 사용자가 LINENO변수를 설정 해제하거나 재설정하면 쉘의 수명 주기 동안 특별한 의미를 잃을 수 있습니다. 쉘이 현재 스크립트나 함수를 실행하고 있지 않으면 값이 LINENO지정되지 않습니다. IEEE Std 1003.1-2001의 이 볼륨은 사용자 이식성 유틸리티 옵션을 지원하는 시스템에 대한 변수의 효과만 지정합니다.

답변3

LINENO = 0스크립트가 종료될 때 실제 줄 번호 대신 을 얻는 것은 ERR(대신 ) 캡처하여 EXIT수정할 수 있습니다.

또한 set -E이러한 ERR 트랩이 함수, 명령 대체 및 하위 쉘 환경에 의해 상속되도록 하기 위해 추가되었습니다. 예를 들어, 스크립트 오류를 ​​일으킨 함수 이름과 해당 줄 번호를 인쇄하는 방법은 다음과 같습니다.

set -eE
trap 'echo "Error in function $FUNCNAME at line $LINENO"' ERR

감사의 말씀:https://citizen428.net/blog/bash-error-handling-with-trap/

답변4

그래서 우연히 bash에서 $LINENO 변수를 올바르게 표시하고(예를 들어 DEBUG 또는 EXIT 트랩에서) 예상하는 줄 번호를 제공하는 방법을 발견했습니다. 다른 공개된 방법보다 훨씬 간단하며 PS4 및 STDERR을 하이재킹할 필요가 없습니다. 잘 모르겠어요이것은 효과가 있지만 다른 사람이 설명할 수 있다면 그 이유를 알고 싶습니다.

기본적으로 실행하려는 항목을 더미 함수(예: 호출 _ff) 로 래핑하고 source <(declare -f _ff)실행하면 _ffin의 모든 인스턴스에 실제로 줄 번호가 부여됩니다.$LINENO_ff

    _ff() (
    trap 'echo "LINENO=$LINENO"' DEBUG;
    echo a
    echo b
    echo c
    echo d
    echo e
    echo f
    echo g
    )
    source <(declare -f _ff)

이제 실행하면 _ff출력됩니다.

LINENO=2
a
LINENO=3
b
LINENO=4
c
LINENO=5
d
LINENO=6
e
LINENO=7
f
LINENO=8
g

참고: 달리기가 source <(declare -f _ff)핵심입니다. 달리지 않으면 LINENO=1모든 $LINENO명령문을 얻게 됩니다.

참고 사항: 이 질문은 꽤 오래되었지만 기본적으로 ERR 트랩 외부에 올바른 $LINENO를 표시하기 위한 작동하는 솔루션을 제공하는 인터넷에서 찾은 유일한 장소입니다. 그래서 여기에 게시하는 것이 적절할 것 같습니다.

관련 정보