향후 변경으로 인해 피해가 발생하지 않도록 bash 스크립트를 강화하려면 어떻게 해야 합니까?

향후 변경으로 인해 피해가 발생하지 않도록 bash 스크립트를 강화하려면 어떻게 해야 합니까?

그래서 홈 폴더(더 정확하게는 쓰기 권한이 있는 모든 파일)를 삭제했습니다. 무슨 일이 있었나요?

build="build"
...
rm -rf "${build}/"*
...
<do other things with $build>

Bash 스크립트에서 $build더 이상 필요하지 않은 선언과 모든 용도를 삭제하세요. 하지만 rmBash는 기꺼이 rm -rf /*이를 확장합니다.

바보 같은 기분이 들어서 백업을 설치하고 잃어버린 작업을 다시 시작했습니다. 부끄러움을 피하려고 노력합니다.

이제 궁금합니다. 그러한 오류가 발생하지 않거나 최소한 발생할 가능성을 낮추기 위해 bash 스크립트를 작성하는 기술은 무엇입니까? 예를 들어 내가 썼다면

FileUtils.rm_rf("#{build}/*")

Ruby 스크립트에서는 인터프리터가 build선언되지 않은 것에 대해 불평하므로 언어가 나를 보호합니다.

목장 외에도 내가 bash에서 고려한 것 rm(관련 질문에 대한 많은 답변에서 언급했듯이 문제가 없는 것은 아닙니다):

  1. rm -rf "./${build}/"*
    그러면 내 현재 작업(Git 저장소)이 파괴되지만 그 외에는 아무것도 없습니다.
  2. rm해당 변형/매개변수화는 현재 디렉터리 외부에서 작동할 때 상호 작용이 필요합니다. (아무 것도 찾을 수 없습니다.) 비슷한 효과입니다.

그렇습니까? 아니면 이런 의미에서 "견고한" bash 스크립트를 작성하는 다른 방법이 있습니까?

답변1

set -u

또는

set -o nounset

이로 인해 현재 쉘은 설정되지 않은 변수의 확장을 오류로 처리합니다.

$ unset build
$ set -u
$ rm -rf "$build"/*
bash: build: unbound variable

set -u그리고 set -o nounsetPOSIX 셸 옵션.

하나비어 있는만날 가치가 있는아니요하지만 오류가 발생합니다.

이렇게 하려면 다음을 사용하세요.

$ rm -rf "${build:?Error, variable is empty or unset}"/*
bash: build: Error, variable is empty or unset

비어 있거나 설정되지 않은 경우 확장은 ${variable:?word}해당 값으로 확장됩니다 . variable비어 있거나 설정되지 않은 경우 word표준 오류에 표시되고 쉘은 확장을 오류로 처리합니다(명령은 실행되지 않으며 비대화식 쉘에서 실행하면 종료됩니다). 생략하면 :아래와 같이 설정되지 않은 값에 대해서만 오류가 발생합니다 set -u.

${variable:?word}POSIX 매개변수 확장.

set -e(또는 set -o errexit)도 적용되지 않는 한 이들 중 어느 것도 대화식 쉘이 종료되지 않습니다 . ${variable:?word}변수가 비어 있거나 설정되지 않은 경우 스크립트가 종료됩니다. set -u와 함께 사용하면 스크립트가 종료됩니다 set -e.


두 번째 질문은. rm현재 디렉터리 외부의 작업을 제한할 수 있는 방법은 없습니다 .

GNU 구현에는 마운트된 파일 시스템을 재귀적으로 제거하는 것을 방지하는 옵션이 있지만 rm실제로 인수를 확인하는 함수에서 호출을 래핑하지 않고도 --one-file-system최대한 가까이 접근 할 수 있다고 생각합니다.rm


참고 사항: 확장이 문자열의 일부로 발생하지 않는 한 ${build}이는 정확히 동일합니다 . 여기서 바로 다음 문자는 in과 같은 변수 이름의 유효한 문자입니다 .$build"${build}x"

답변2

test일반적인 유효성 검사에는 /를 사용하는 것이 좋습니다 .[ ]

다음과 같이 스크립트를 작성하면 안전합니다.

build="build"
...
[ -n "${build}" ] || exit 1
rm -rf "${build}/"*
...

수표 는[ -n "${build}" ]"${build}"길이가 0이 아닌 문자열.

||Bash의 논리 OR 연산자 입니다 . 첫 번째 명령이 실패하면 다른 명령이 실행됩니다.

그렇게 하면 ${build}이미 비어 있거나 정의되지 않은 상태입니다. 스크립트가 종료됩니다(반환 코드 1, 이는 일반 오류임).

이는 항상 가짜이므로 ${build}모든 용도를 제거해도 보호받을 수 있습니다 .[ -n "" ]


test[ ]/를 사용하면 더 많은 의미 있는 검사를 사용할 수 있다는 장점이 있습니다.

예를 들어:

[ -f FILE ] True if FILE exists and is a regular file.
[ -d FILE ] True if FILE exists and is a directory.
[ -O FILE ] True if FILE exists and is owned by the effective user ID.

답변3

귀하의 특정한 경우에는 과거에 파일/디렉토리를 이동하기 위해 "삭제"를 수정했습니다(/tmp가 귀하의 디렉토리와 동일한 파티션에 있다고 가정).

# mktemp -d is also a good, reliable choice
trashdir=/tmp/.trash-$USER/trash-`date`
mkdir -p "$trashdir"
...
mv "${build}"/* "$trashdir"
...

$trashdir뒤에서는 디렉터리 구조를 탐색하고 각 파일에 대한 디스크 블록을 즉시 해제하는 데 시간을 소비하지 않고 소스에서 동일한 파티션의 대상 디렉터리 구조로 최상위 파일/디렉터리 참조를 이동합니다 . 이로 인해 시스템이 활성 사용 중일 때 정리 속도가 빨라지고 재부팅 속도가 약간 느려집니다(재부팅 시 /tmp가 정리됨).

또는 많은 양의 디스크 공간을 소비하는 프로세스(예: 빌드)의 경우 /tmp/.trash-$USER에 대한 cron 항목을 정기적으로 정리하면 /tmp가 가득 차는 것을 방지할 수 있습니다. 디렉토리가 /tmp와 다른 파티션에 있는 경우 파티션에 /tmp와 같은 디렉토리를 생성하고 cron에서 이를 정리하도록 할 수 있습니다.

그러나 무엇보다도 가장 좋은 점은 어떤 식으로든 변수를 엉망으로 만든 경우 정리가 발생하기 전에 내용을 복원할 수 있다는 것입니다.

답변4

나는 항상 한 줄로 Bash 스크립트를 시작하려고 노력합니다 #!/bin/bash -ue.

-e'첫 번째 실패'를 의미이자형실수";

-u"첫 번째 사용에 실패했음을 의미합니다.n변수 선언".

이 훌륭한 기사에서 자세한 내용을 찾아보세요비공식 Bash 엄격 모드 사용(디버깅을 좋아하지 않는 한). 저자는 또한 사용을 권장 set -o pipefail; IFS=$'\n\t'하지만 내 목적에 있어서 이것은 약간 과잉입니다.

관련 정보