bash 스크립트가 나중에 변경 될 때 피해를 입지 않도록 어떻게 강화할 수 있습니까?


46

따라서 홈 폴더 (또는 더 정확하게는 쓰기 권한이있는 모든 파일)를 삭제했습니다. 무슨 일이 있었는지

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

bash 스크립트에서 더 이상 필요하지 않은 $build경우 선언과 모든 사용법을 제거합니다 rm. 배시는 행복하게 확장됩니다 rm -rf /*. 네.

나는 바보를 느꼈고, 백업을 설치했고, 잃어버린 일을 다시했다. 부끄러운 곳을지나 가려고합니다.

이제 나는 bash 스크립트를 작성하여 그러한 실수가 발생하지 않거나 적어도 덜 가능성이있는 기술이 무엇인지 궁금합니다. 예를 들어, 내가 쓴 적이

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

루비 스크립트에서 통역사는 build선언되지 않은 것에 대해 불평 했을 것이므로 언어가 나를 보호합니다.

상관 관계 외에도 bash에서 내가 고려한 것 rm(관련 질문에 대한 많은 답변에서 언급 했듯이 문제가되지 않음) :

  1. rm -rf "./${build}/"*
    그것은 내 현재 작업 (Git repo)을 죽였을 것입니다.
  2. rm현재 디렉토리 외부에서 작업 할 때 상호 작용이 필요한 변형 / 매개 변수입니다 . (아무것도 찾을 수 없습니다.) 비슷한 효과.

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


3
rm -rf "${build}/*인용 부호의 위치에 상관 없이 이유가 없습니다. rm -rf "${build} 때문에 같은 일을 할 것입니다 f.
Monty Harder

1
shellcheck.net사용한 정적 검사 는 시작하기에 매우 견고한 장소입니다. 사용할 수있는 편집기 통합이 있으므로 여전히 사용중인 대상의 정의를 제거하자마자 도구에서 경고를받을 수 있습니다.
찰스 더피

내 수치에 @CharlesDuffy하려면 추가 기능, 나는 BashSupport 설치와 함께 IDEA 스타일의 IDE에 해당 스크립트를 썼다 않는 경우에 경고한다. 예, 유효 합니다만, 정말로 취소가 필요했습니다.
Raphael

2
알았어 BashFAQ # 112의주 의 사항에 유의하십시오 . set -u거의 찌그러 set -e지지는 않지만 여전히 문제가 있습니다.
찰스 더피

2
농담 답변 : #! /usr/bin/env ruby모든 쉘 스크립트의 맨 위에 놓고 bash를 잊어 버려라.)
Pod

답변:


75
set -u

또는

set -o nounset

이것은 현재 쉘이 설정되지 않은 변수의 확장을 오류로 취급하게합니다.

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

set -u하고 set -o nounset있는 POSIX 쉘 옵션 .

값 것이다 되지 하지만 오류를 트리거합니다.

이를 위해

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

의 확장 ${variable:?word}의 값으로 확장 할 variable비어 또는 해제 아니라면. 비어 있거나 설정되지 않은 경우 word표준 오류로 표시되고 셸은 확장을 오류로 처리합니다 (명령이 실행되지 않으며 비 대화식 셸에서 실행중인 경우 종료 됨). :out을 그대로 두면 아래처럼 설정되지 않은 값에 대해서만 오류가 발생합니다 set -u.

${variable:?word}A는 POSIX 파라미터 확장 .

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


두 번째 질문은 rm현재 디렉토리 외부에서 작동하지 않도록 제한 할 방법이 없습니다 .

GNU 구현 rm에는 --one-file-system마운트 된 파일 시스템을 재귀 적으로 삭제하지 못하게 하는 옵션이 있지만 rm실제로 인수를 확인하는 함수로 호출을 래핑하지 않고 얻을 수 있다고 생각 합니다.


참고로 : 바로 다음 문자가 변수 이름의 유효한 문자 (예 : in)와 같이 문자열의 일부로 확장이 발생하지 않는 한 ${build}정확하게 동일합니다 .$build"${build}x"


아주 좋아 고마워! 1) ${build:?msg}아니면 ${build?msg}입니까? 2) 빌드 스크립트와 같은 맥락에서 더 안전한 삭제를 위해 rm과 다른 도구를 사용하는 것이 좋습니다. 우리 는 현재 디렉토리 외부에서 작업하고 싶지 않다는 것을 알고 있습니다. 명령. rm을 더 안전하게 만들 필요는 없습니다.
Raphael

1
@Raphael 죄송합니다. 나는 지금 그 오타를 바로 잡을 것이고, 나중에 내 컴퓨터로 돌아올 때 그 중요성을 언급 할 것이다.
Kusalananda

2
@Raphael Ok, 나는 :( 없이 설정 된 변수에 대해서만 오류를 유발할 것입니다) 없이 발생하는 것에 대한 짧은 문장을 추가했습니다 . 모든 상황에서 특정 경로에서 파일을 독점적으로 삭제하는 데 도움이되는 도구를 작성하려고하지 않습니다. 경로를 파싱하고 심볼릭 링크 등을 돌 보거나 돌보지 않는 것은 현재로서는 너무 어리 석습니다.
Kusalananda

1
감사합니다. 설명이 도움이됩니다! "로컬 rm"에 대하여 : 나는 주로 "어쩌면 <toolname>"을 위해 낚시를하고 있었지만 어쩌면 당신이 뱀파이어를 쓰도록 뱀파이어를 시도하지는 않았습니다! OO 좋습니다. 충분히 도와 주셨습니다! :)
Raphael

2
@MateuszKonieczny 나는 그렇게 생각하지 않습니다, 죄송합니다. 필요할 때 이와 같은 안전 조치를 사용해야한다고 생각 합니다 . 환경을 안전하게 만드는 모든 것과 마찬가지로 결국 사람들은 점점 더 부주의하고 안전 조치에 의존하게 될 것입니다. 각각의 모든 안전 조치가 무엇을하는지 알고 필요에 따라 선택적으로 적용하는 것이 좋습니다.
Kusalananda

12

test/를 사용하여 일반적인 유효성 검사를 제안합니다.[ ]

다음과 같이 스크립트를 작성하면 안전했을 것입니다.

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

[ -n "${build}" ]검사 "${build}"A는 비 - 제로 길이 스트링 .

||떠들썩한 파티에서 논리 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.

공정하지만, 다루기 힘들다. 내가 뭔가를 놓치고 ${variable:?word}있습니까 , 아니면 @ Kusalananda가 제안한 것과 거의 똑같습니까?
Raphael

@Raphael은 "문자열에 값이 있습니까?"의 경우와 유사하게 작동하지만 test(즉 [, -d(인수는 디렉토리 임), -f(인수는 파일 임), -O( 파일 과 같은) 관련된 다른 많은 검사가 있습니다. 현재 사용자가 소유하고 있습니다).
Centimane

1
@Raphael은 또한 유효성 검사는 코드 / 스크립트의 일반적인 부분이어야합니다. 또한 빌드의 모든 인스턴스를 제거한 경우 ${build:?word}함께 제거하지 않았 습니까? ${variable:?word}당신은 변수의 모든 인스턴스를 제거하는 경우 형식은 당신을 보호하지 않습니다.
Centimane

1
1) 가정을 유지하는지 (파일이 있는지 등) 확인하고 변수가 설정되어 있는지 확인하는 것 (컴파일러 / 인터프리터의 중요한 작업)에는 차이가 있다고 생각합니다. 내가 후자를 직접해야한다면, 그것을 위해 매우 멋진 구문이 더 낫습니다 if. 2) "$ {build :? word}와 함께 $ {build :? word}도 제거하지 않았습니까?"시나리오는 변수 사용법을 놓친 것입니다. 사용하면 ${v:?w}손상으로부터 보호 할 수있었습니다. 모든 사용을 제거했다면 일반 액세스조차도 해롭지 않았을 것입니다.
Raphael

모두가 말했듯이, 당신의 대답은 일반적인 제목 질문에 대한 공정하고 견실 한 답변입니다. 가정을 유지 하는 것이 주위에있는 스크립트에 중요합니다. 질문 본문의 특정 문제는 Kusalananda가 더 잘 대답합니다. 감사!
Raphael

4

특정 경우에는 파일 / 디렉토리를 대신 이동하기 위해 과거에 '삭제'를 재 작업했습니다 (/ 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을 정리할 수 있습니다.

그러나 가장 중요한 것은 변수를 어떤 식 으로든 망쳐 놓으면 정리하기 전에 내용을 복구 할 수 있습니다.


2
"시스템을 사용하는 동안 훨씬 빠른 정리가 가능합니다." 두 가지 모두 영향을받는 inode 만 변경하는 것으로 생각했습니다. 다른 파티션에있는 경우 확실히 빠르지 않습니다/tmp (적어도 사용자 공간에서 실행되는 스크립트는 항상 나에게 적합하다고 생각합니다). 그런 다음 휴지통 폴더를 변경해야합니다 (및 OS 처리에서 이익을 얻지 못함 /tmp).
Raphael

1
mktemp -d다른 프로세스의 발가락을 밟지 않고 임시 디렉토리를 얻는 데 사용할 수 있습니다 (그리고 올바르게 존중하기 위해 $TMPDIR).
Toby Speight

두 분의 정확하고 유용한 메모를 추가했습니다. 감사합니다. 나는 당신이 제기 한 요점을 고려하지 않고 원래 이것을 너무 빨리 게시했다고 생각합니다.
user117529

2

변수가 초기화되지 않은 경우 bash 매개 변수 대체를 사용하여 기본값을 지정하십시오. 예 :

rm -rf $ {변수 :- "/ 존재하지 않음"}


1

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

-e수단은 "제 uncathed 실패 전자 rror";

-u수단 "의 최초 사용에 실패 변수 ndeclared".

훌륭한 기사에서 자세한 내용을 알아보십시오 . 비공식 Bash 엄격한 모드 사용 (Looove 디버깅이 아닌 한) . 또한 저자는 사용을 권장 set -o pipefail; IFS=$'\n\t'하지만 내 목적으로는 과잉입니다.


1

변수가 설정되어 있는지 확인하는 일반적인 조언은 이러한 종류의 문제를 방지하는 유용한 도구입니다. 그러나이 경우 더 간단한 해결책이있었습니다.

$build디렉토리를 삭제하기 위해 디렉토리 의 내용을 가져갈 필요는 없지만 실제 $build디렉토리 자체 는 아닙니다 . 따라서 외부 *를 건너 뛰면 설정되지 않은 값이 설정 rm -rf /됩니다. 기본적으로 지난 10 년 동안 대부분의 rm 구현은 수행을 거부합니다 (GNU rm --no-preserve-root옵션 으로이 보호 기능을 비활성화하지 않는 한 ).

후행 /도 건너 뛰면 rm ''오류 메시지가 나타납니다.

rm: can't remove '': No such file or directory

rm 명령이에 대한 보호를 구현하지 않더라도 작동합니다 /.


-2

프로그래밍 언어 용어로 생각하고 있지만 bash 는 스크립팅 언어입니다 :-) 따라서 완전히 다른 언어 패러다임에 대해 완전히 다른 명령 패러다임을 사용하십시오 .

이 경우 :

rmdir ${build}

이후 rmdir비어 있지 않은 디렉토리를 제거하는 것을 거부합니다, 먼저 구성원을 제거해야합니다. 그 멤버들이 뭔지 알아? 아마도 스크립트의 매개 변수이거나 매개 변수에서 파생 된 것일 수 있습니다.

rm -rf ${build}/${parameter}
rmdir ${build}

이제 임시 파일이나 원하지 않는 것과 같은 다른 파일이나 디렉토리를 넣으면 rmdir오류가 발생합니다. 올바르게 처리 한 후 다음을 수행하십시오.

rmdir ${build} || build_dir_not_empty "${build}"

이런 생각은 저에게 잘 도움이되었습니다.


6
노력해 주셔서 감사합니다. 그러나 이것은 완전히 벗어난 것입니다. "그 멤버들이 무엇인지 알고있다"는 가정은 틀렸다. 컴파일러 출력을 생각하십시오. make clean매우 부드러운 컴파일러가 작성하는 모든 파일의 목록을 작성하지 않는 한 일부 와일드 카드를 사용합니다. 또한 rm -rf ${build}/${parameter}문제를 조금 아래로 이동시키는 것 같습니다 .
Raphael

흠. 어떻게 읽었는지보세요. "감사합니다.하지만 이것은 원래 질문에 공개하지 않은 내용 이므로 일반적인 경우에 더 적합하지만 답변을 거부 할뿐 아니라 공감할 것입니다." 와.
Rich

"질문에 쓴 내용 이외의 추가 가정을 한 다음 전문 답변을 작성하겠습니다." * 어깨를 으 F * (FYI, 그것은 당신의 사업 이 아닙니다 : 나는 공감하지 않았습니다. 대답은 도움이되지 않았기 때문에 (적어도 나에게는) 그렇지 않을 것입니다.)
Raphael

흠. 나는 가정을하지 않았고 일반적인 대답을 썼습니다 . 이 make질문 에는 전혀 아무것도 없습니다 . 적용 가능한 답변을 작성하는 make것은 매우 구체적이고 놀라운 가정입니다. 내 답변은 make소프트웨어 개발 이외의 경우, 물건을 삭제하여 발생한 특정 초보자 문제 이외의 경우에 작동합니다 .
Rich

1
Kusalananda는 낚시하는 법을 가르쳐주었습니다. 너는 와서 말했다. "너는 평원에 살면서 왜 대신 쇠고기를 먹지 않니?"
Raphael
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.