쉘 스크립트에서 정리 오류

쉘 스크립트에서 정리 오류

쉘 스크립트에 대한 오류 정리 논리를 포함하는 가장 좋은 방법은 무엇입니까?

특히 다음을 수행하는 스크립트가 있습니다.

mount a x
mount b y
setup_thing
mount c z
do_something
umount z
cleanup_thing
umount y
umount x

모든 마운트와 do_something자체가 실패할 수 있습니다. 예를 들어, mount c z설치가 실패할 경우 종료하기 전에 성공적으로 설치된 설치를 제거하는 스크립트를 원합니다.

정리 코드를 여러 번 반복하고 싶지도 않고 모든 것을 if-nest로 래핑하고 싶지도 않습니다(마운트를 추가하면 모든 것을 다시 들여쓰기해야 하기 때문입니다).

위의 내용을 다음과 같이 작성할 수 있도록 최종 스택 등을 생성하는 방법이 있습니까?

set -e
mount a x && finally umount x
mount b y && finally umount y
setup_thing && finally cleanup_thing
mount c z && finally umount z
do_something

"finally" 명령이 등록되면 종료, 통과 또는 실패 시 (역순으로) 실행됩니다. 그러나 성공적으로 설정되지 않은 것은 정리되지 않습니다(설치가 실패하면 정리가 안전하지 않을 수 있기 때문입니다). 실행).

즉, 모든 것이 성공하면 , , umount z, - 실패만 하는 경우에도 마찬가지 의 순서로 실행됩니다 . 하지만 실패할 경우에만 실행됩니다 .cleanup_thingumount yumount xdo_somethingmount b yumount x

(실패 시 정확한 종료 코드를 유지할 필요는 없지만 0 또는 0이 아닌 값으로 적절하게 종료해야 하는지 여부입니다.)

종료 시 명령을 실행할 수 있는 내장 기능 이 있다는 것을 알고 있지만 trap이는 하나의 명령만 지원하고 매번 이를 대체합니다. 이것을 위와 같은 스택으로 확장하는 방법이 있습니까? 아니면 이것을 수행하는 다른 깨끗한 방법이 있습니까?

Ye Olde C 코드의 오류 처리 패턴에서 비슷한 결과를 얻을 수 있지만 x || goto cleanup_before_x물론 goto가 없습니다.

이상적으로는 (da)sh에서 작동하지만, 그것이 단순화된다면 bash도 요청할 수 있습니다. (어쩌면 배열을 사용하고 있을까요?)

답변1

trap일반적인 기술은 작업이 완료되면 모든 항목 실행 취소를 사용하는 것입니다. 멱등성이어야 합니다. 즉, 일부 단계를 완료할 수 없는 경우 정상적으로 실패해야 합니다.

#!/bin/bash

clean_up=false
errorhandler () {
    umount z || true
    $clean_up && cleanup_thing
    umount y || true
    umount x
}
trap errorhandler ERR EXIT

mount a x
mount b y
setup_thing
clean_up=true
mount c z
do_something

트랩도 트리거되므로 EXIT스크립트가 정상적으로 종료되기 전에도 실행되므로 명시적으로 정리할 필요가 없습니다.

나는 가짜 ERR신호가 Bash 확장이라고 생각합니다. 따라서 이것은 Ash/Dash/레거시 Bourne 쉘 등에서는 작동하지 않습니다.

답변2

기존 마운트 지점을 별도로 처리하도록 수정되었습니다. 코드가 이 형식이라고 가정하면 문제를 일으킬 수 있는 줄에는 한 줄에 하나의 명령이 있습니다. 즉, mount다음과 같습니다 umount.

mount a x
mount b y
setup_thing
mount c z
do_something
umount z
cleanup_thing
umount y
umount x

이 패치워크가 작동할 수도 있습니다. 이 코드를 스크립트의 두 번째 줄에 복사하세요.

mount | cut -d' ' -f3 | sed 's/.*/^u\?mount [^[:space:]]\* &$/' | 
  grep -v -f - $0 | sed 2d | exec sh -s -- "$@"

작동 방식(이론적으로):

  1. 기존 마운트 지점 목록을 생성하고 그대로 두려면 mount및 를 사용하십시오 .cut
  2. 이러한 마운트 지점을 사용하는 패턴이나 명령과 일치하는 패턴 목록을 만드는 데 사용됩니다 sed.grepmountumount
  3. grep -v현재 스크립트에서 모든 줄을 검색 하는 데 사용됩니다.아니요이전 패턴 목록과 일치합니다 grep. 실행하기에 적합한 모든 코드를 그대로 둡니다.
  4. sed재귀를 방지하기 위해 두 번째 행을 삭제하는 데 사용됩니다 .
  5. exec sh -s -- "$@"명령줄 인수와 함께 해당 코드를 실행하는 데 사용됩니다 . 해당 선 위의 exec항목은 실행되지 않습니다.

exec변경하고 cat #출력을 확인하여 미리 테스트하세요. 괜찮아 보이면 exec다시 넣으세요.

답변3

if then개인적으로 나는 읽고 유지하기가 더 쉽기 때문에 중첩을 선택합니다 . 그러나 중첩 수준이 많으면 다음과 같이 시도해 볼 수 있습니다( echo접두사로 테스트했습니다).

#!/bin/bash
run1(){ echo mount a x;}
run2(){ echo mount b y;}
run3(){ echo setup_thing;}
run4(){ echo mount c z;}
run5(){ echo do_something;}

undo5(){ :;}
undo4(){ echo umount z;}
undo3(){ echo cleanup_thing;}
undo2(){ echo umount y;}
undo1(){ echo umount x;}

for i in {1..5}
do      run$i
        code=$?
        [ $code != 0 ] && break
done
let i=i-1
while [ $i -gt 0 ]
do      undo$i
        let i=i-1
done
exit $code

예제의 순서대로 실행 및 실행 취소 기능을 유지했지만 서로 가까이 배치하면 이점을 얻을 수 있습니다.

run1(){  mount a x;}
undo1(){ umount x;}
run2(){  mount b y;}
undo2(){ umount y;}
run3(){  setup_thing;}
undo3(){ cleanup_thing;}
...

함수에 1,2,3... 번호를 매기는 대신 실행 함수의 이름을 지정한 다음 원하는 순서대로 이름을 나열할 수 있습니다. 실행 취소 기능을 더 쉽게 만들려면 일관된 접두사를 추가하세요.

mnta(){ ... }
undomnta(){ ... }
mntb(){ ... }
undomntb(){ ... }

order='mnta mntb ...' toundo=
for i in $order
do      $i
        code=$?
        [ $code != 0 ] && break
        toundo="undo$i $toundo"
done
for i in $toundo
do  $i
done

또는 실제로 를 사용할 수도 있지만 trap한 번만 설정하고 trap mytrap exit전역 변수를 사용하여 정리하려는 항목을 보관하고 각 단계에 추가할 수 있습니다 clean="1 $clean". 그런 다음 함수는 mytrap의 값만 반복합니다 $clean.

관련 정보