쉘 스크립트에 대한 오류 정리 논리를 포함하는 가장 좋은 방법은 무엇입니까?
특히 다음을 수행하는 스크립트가 있습니다.
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_thing
umount y
umount x
do_something
mount b y
umount 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 -- "$@"
작동 방식(이론적으로):
- 기존 마운트 지점 목록을 생성하고 그대로 두려면
mount
및 를 사용하십시오 .cut
- 이러한 마운트 지점을 사용하는 패턴이나 명령과 일치하는 패턴 목록을 만드는 데 사용됩니다
sed
.grep
mount
umount
grep -v
현재 스크립트에서 모든 줄을 검색 하는 데 사용됩니다.아니요이전 패턴 목록과 일치합니다grep
. 실행하기에 적합한 모든 코드를 그대로 둡니다.sed
재귀를 방지하기 위해 두 번째 행을 삭제하는 데 사용됩니다 .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
.