서브쉘에서 dirname을 사용할 때 문제가 발생합니다.

서브쉘에서 dirname을 사용할 때 문제가 발생합니다.

나는 하위 디렉터리를 통해 반복되어야 하는 (한 줄) 스크립트를 작성하고 있습니다. 하이퍼링크가 포함된 .txt 파일을 찾습니다. wget을 사용하여 콘텐츠를 가져와 텍스트 파일과 동일한 디렉터리에 다운로드합니다.

발견된 모든 텍스트 파일에는 유효한 하이퍼링크만 포함되어 있다고 가정됩니다.

이를 테스트하려면:
하위 디렉터리를 만듭니다. 내용이 포함된 ./s1
텍스트 파일을 만듭니다 . ./s1/s1.txt
./s1/s1.txtwww.google.com

다음 줄은 다음과 같습니다.

find . -type f -name "*.txt" -exec bash -cx "wget -i \"{}\" -P  $(dirname \"{}\") " \;

문제는 $(dirname \"{}\")올바르게 확장되지 않는다는 것입니다. 실행되는 bash 명령은 다음과 같습니다.

+ wget -i ./s1/s1.txt -P .

따라서 $(dirname \"{}\")반환된 .
효과는 새로운 것입니다.목차 ./s1/s1.txt건설되다. 따라서 다운로드한 파일은 다음과 같이 저장됩니다../s1/s1.txt/index.html

내가 교체하면 $(dirname \"{}\")출력 $(echo \"{}\")은 다음과 같습니다.

+ wget -i ./s1/s1.txt -P ./s1/s1.txt

따라서 매개변수 전달 자체가 정확합니다. 따라서 결과가 dirname호출 bash 셸에 올바르게 반환되지 않는다고 가정합니다 . 아니면 dirname전혀 평가하지 마세요.

방금 bash 명령을 실행할 때

bash -cx "wget -i ./s1/s1.txt -P  $(dirname ./s1/s1.txt)" 

(따라서 find명령 외부) 명령은 예상대로 실행됩니다.

+ wget -i ./s1/s1.txt -P ./s1

이 선을 작동시키는 올바른 방법은 무엇입니까?

답변1

여기에서 다음을 수행할 수 있습니다.

find . -name '*.txt' -type f -execdir wget -i {} -P . ';'

발견된 파일의 디렉토리에서 명령을 실행하는 대신 비표준이지만 매우 일반적인 -execdir조건자를 사용하십시오(그리고 GNU를 포함한 일부 구현이 앞에 올 수 있는 전체 경로 대신 파일 이름으로 확장됨 ).find-exec{}./findfind

GNU를 사용하면 find일부 xargs를 병렬로 실행할 수 있습니다.

xargs -r0 -n4 -P10 -a <(
  find . -name '*.txt' -type f -printf '-i\0%p\0-P\0%h\0'
  ) wget

find인수 목록을 작성 하고 wget이를 NUL로 구분하여 출력합니다(0은 파일 경로에 대한 외부 명령줄 인수에 나타날 수 없는 유일한 바이트 값입니다). xargs한 번에 최대 병렬로 인스턴스를 실행합니다.4wget10P

존재하다 zsh:

for file (**/*.txt(N.)) wget -i $file -P $file:h

(다음에 추가D 글로벌 예선find메소드에서 와 같이 숨겨진 파일도 처리하려는 경우 ).


당신의

find . -type f -name "*.txt" -exec bash -cx "wget -i \"{}\" -P  $(dirname \"{}\") " \;

은 큰따옴표 안에 있으므로 명령을 입력한 셸은 명령을 전달하기 전에 $(...)출력으로 확장합니다 .dirname \"{}\"find

dirname \"{}\", sh/bash의 dirname '"{}"'출력(현재 작업 디렉터리 경로)과 동일합니다.dirname anything-that-does-not-contain-a-slash-and-does-not-start-with-dash.

따라서 find는 다음 인수로 호출됩니다.

  1. find
  2. .
  3. -type
  4. f
  5. -name
  6. *.txt
  7. -exec
  8. bash
  9. -cx
  10. wget -i "{}" -P .
  11. ;

find다음 매개변수 로 실행 됩니다 bash.

  1. bash
  2. -cx
  3. wget -i "./path/to/the/file.txt" -P .

발견된 각 파일에 대해 bash는 다음을 실행합니다 wget.

  1. wget
  2. -i
  3. ./path/to/the/file.txt
  4. -P
  5. .

하지만 만약에파일 경로\포함되면 잠재적으로 재앙이 될 수 있는 , , "또는 `문자 를 포함하지 않습니다 "(예: 이름이 파일인 경우 $(rm -rf ~).txt).

큰따옴표 대신 작은따옴표를 사용하는 경우:

find . -type f -name "*.txt" -exec bash -cx 'wget -i "{}" -P  "$(dirname "{}")"' \;

수정되었을 수도 있지만 위에서 언급한 이유로 인해 여전히 매우 잘못된 상태입니다. {}~해야 한다안 돼요코드로 계산된 매개변수에 내장됩니다. 바라보다@gils의 답변이를 올바르게 수행하는 방법.


1 -execdirAFAIK OpenBSD에서 1996년 FreeBSD, 1997년 FreeBSD, 2002년 NetBSD, find2005년 GNU, 2010년 sfind, 2014년 최소한 toybox에 추가되었습니다.

답변2

의견에서 말했듯 find이 bash 부분에서 자리 표시자를 사용하지 마십시오. {}이것은 신뢰할 수 없으며 가능합니다.보안 문제(셸 주입).

이 방법을 사용하는 것이 더 좋습니다:

 find . -type f -name '*.txt' -exec sh -c '
     for file; do
         wget -i "$file" -P "$(dirname "$file")"
     done
 ' sh {} +

또는 표준을 사용하십시오매개변수 확장(더 효율적인 것 외에도 디렉토리 이름이 개행 문자로 끝나는 경우에도 여전히 작동한다는 장점이 있습니다):

 find . -type f -name '*.txt' -exec sh -c '
     for file; do
         wget -i "$file" -P "${file%/*}"
     done
 ' sh {} +

$ tree
.
└── s1
    ├── index.html
    └── s1.txt

1 directory, 2 files

관련 정보