공백을 가진 파일 이름을 처리하기위한 BASH 스크립트`for` 만들기 (또는 해결 방법)


12

몇 년 동안 BASH를 사용해 왔지만 BASH 스크립팅에 대한 내 경험은 상대적으로 제한적입니다.

내 코드는 다음과 같습니다. 현재 디렉토리 내에서 전체 디렉토리 구조를 가져 와서로 복제해야합니다 $OUTDIR.

for DIR in `find . -type d -printf "\"%P\"\040"`
do
  echo mkdir -p \"${OUTPATH}${DIR}\"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done

문제는 내 파일 구조의 샘플입니다.

$ ls
Expect The Impossible-Stellar Kart
Five Iron Frenzy - Cheeses...
Five Score and Seven Years Ago-Relient K
Hello-After Edmund
I Will Go-Starfield
Learning to Breathe-Switchfoot
MMHMM-Relient K

공백을 참고하십시오 : -S 그리고 for단어별로 매개 변수를 사용하므로 스크립트의 출력은 다음과 같습니다.

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Learning"
Created Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot"
Created Breathe-Switchfoot

그러나의 출력에서 ​​전체 파일 이름 (한 번에 한 줄)을 가져 와야 find합니다. 또한 find각 파일 이름 주위에 큰 따옴표 를 넣으 려고했습니다 . 그러나 이것은 도움이되지 않습니다.

for DIR in `find . -type d -printf "\"%P\"\040"`

그리고이 변경된 줄로 출력 :

Creating directory structure...
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"""
Created ""
mkdir -p "/myfiles/multimedia/samjmusicmp3test/"Learning"
Created "Learning
mkdir -p "/myfiles/multimedia/samjmusicmp3test/to"
Created to
mkdir -p "/myfiles/multimedia/samjmusicmp3test/Breathe-Switchfoot""
Created Breathe-Switchfoot"

이제 gstreamer다음과 유사한 구조로 각 파일에 대해 보다 복잡한 명령을 실행하고 싶기 때문에 이와 같이 반복 할 수있는 방법이 필요합니다 . 어떻게해야합니까?

편집 : 각 디렉토리 / 파일 / 루프마다 여러 줄의 코드를 실행할 수있는 코드 구조가 필요합니다. 확실하지 않으면 죄송합니다.

솔루션 : 처음에 시도했습니다 :

find . -type d | while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done

이것은 대부분 잘 작동했습니다. 그러나 나중에 파이프가 서브 쉘에서 while 루프를 실행하기 때문에 루프에 설정된 모든 변수를 사용할 수 없으므로 오류 카운터를 구현하기가 매우 어려웠습니다. 내 최종 솔루션 ( 이 답변에서 SO ) :

while read DIR
do
  mkdir -p "${OUTPATH}${DIR}"
  echo Created $DIR
done < <(find . -type d)

나중에 루프에서 변수를 조건부로 증가시켜 나중에 스크립트에서 계속 사용할 수 있습니다.


Why_would_you_ever_need_a_space_in_a_file_name?
Kevin Panko

사실, 내 취향이 아닙니다. 그러나 공백을 제거하려면 먼저 공백이있는 파일을 처리해야합니다.)
Samuel Jaeschke

1
실제로 파일 이름은 공백을 허용해야합니다. /인쇄 할 수없는 문자 만 허용 합니다. 그러나 모든 것을 제외 /하고 는 허용 \0되므로 허용 해야합니다.
Kevin Panko

답변:


11

당신은 파이프에 필요 findwhile루프.

find ... | while read -r dir
do
    something with "$dir"
done

또한 -printf이 경우에는 사용할 필요가 없습니다 .

널 바이트 분리 문자 (* nix 파일 경로에 표시 할 수없는 유일한 문자)를 사용하여 원하는 경우 이름에 개행이있는 파일에 대해이 증거를 작성할 수 있습니다.

find ... -print0 | while read -d '' -r dir
do
    something with "$dir"
done

또한 $()보다 다양하고 쉽게 사용할 수 있도록 백틱 대신 사용 하는 방법을 찾을 수 있습니다. 그것들은 훨씬 쉽게 중첩 될 수 있으며 인용은 훨씬 쉽게 수행 될 수 있습니다. 이 예에서는 다음 사항을 설명합니다.

echo "$(echo "$(echo "hello")")"

백틱으로 해보십시오.


2
또한, 대신 "$dir"에 사용하는 것이 바람직합니다. "${dir}"$ {dir} name과 $ {dirname}의 차이점을 쉽게 알 수 있지만 $ dirname은 어느 쪽이든 해석 될 수 있습니다.
James Polley

여기서 중요한 것은 read전체 줄을로 읽어서 ${dir}IFS가 중요하지 않다는 것입니다.
James Polley

1
. 아무 것도 변수 이름 다음에 거기에없는 경우 $ / "오타를 찾아 주셔서 감사합니다 중괄호는 필요하지 않습니다.
일시 중지 추후 공지가있을 때까지.

4
공백이있는 경로 이름 (U + 0020)은 처리하지만 줄 바꿈 (U + 000A)이있는 경로 이름은 여전히 ​​제대로 처리하지 못합니다. find … -print0 | xargs -0 …POSIX pathanames에서 허용되지 않는 유일한 문자 인 NUL (U + 0000)과 정확히 일치하기 때문에 사용하는 구분 기호를 선호합니다 .
Chris Johnsen

2
완전한! 내가 찾던 것. 당신이에 파이프 수있는 것은 나에게 결코 발생하지 않았다 while. @Chris Johnsen : 사실이지만 음악 추출 프로그램도 파일 이름에 줄 바꿈을 넣는 경향이 없습니다. 그리고 만일 그렇다면, 알고 싶습니다 (예 : 무언가 잘못됨). 즉시 제거하십시오.
Samuel Jaeschke

8

공백이있는 파일 이름을 처리하는 스크립트의 예는 며칠 전에 작성한 이 답변을 참조하십시오 .

당신이하려고하는 것을 달성하기 위해 약간 더 복잡한 (그러나 더 간결한) 방법이 있습니다 :

find . -type d -print0 | xargs -0 -I {} mkdir -p ../theredir/{}

-print0find에게 인수를 널로 분리하도록 지시합니다. xargs에 -0은 널로 분리 된 인수를 예상하도록 지시합니다. 이것은 공간을 잘 처리한다는 것을 의미합니다.

-I {}xargs에게 문자열 {}을 파일 이름 으로 바꾸도록 지시 합니다. 이것은 또한 커맨드 라인 당 하나의 파일 이름 만 사용해야 함을 의미합니다 (xargs는 일반적으로 줄에 맞는만큼 채 웁니다)

나머지는 분명해야합니다.


그러나 Dennis Williamson의 제안은 (오타 제외) 훨씬 더 읽기 쉽고 거의 모든면에서 바람직합니다.
James Polley

mkdir의 경우 작동하지만 더 명확해야합니다. 각 파일에 대해 일련의 명령을 실행하고 싶습니다. 나중에 비슷한 루틴을 위해 입력 파일 이름 (.ogg 확장명 제거 및 .mp3 추가 포함)을 기반으로 출력 파일 이름을 생성 한 다음 gst-launch를 호출 할 때 pipline에서 이러한 여러 변수를 사용하려고합니다.
Samuel Jaeschke

5

당신이 겪고있는 문제는 for 문이 찾기에 별도의 인수로 응답한다는 것입니다. 공백 분리 문자. 공간에서 분할되지 않도록 bash의 IFS 변수를 사용해야합니다.

여기입니다 링크 이 작업을 수행하는 방법에 대해 설명합니다.

IFS 내부 변수

이 문제를 해결하는 한 가지 방법은 Bash의 내부 IFS (Internal Field Separator) 변수를 변경하여 필드를 기본 공백 (공백, 탭, 줄 바꾸기) 이외의 다른 필드 (이 경우 쉼표)로 나누도록하는 것입니다.

#!/bin/bash
IFS=$';'

for I in `find -type d -printf \"%P\"\;`
do
   echo "== $I =="
done

% P 다음에 필드 분리 문자를 출력하도록 찾기를 설정하고 IFS를 적절하게 설정하십시오. 세미콜론은 파일 이름에서 찾을 가능성이 거의 없으므로 선택했습니다.

다른 대안은 찾기를 통해 직접 mkdir을 호출하는 것 -exec입니다. for 루프를 모두 건너 뛸 수 있습니까? 추가 구문 분석이 필요하지 않은 경우입니다.


파일 이름에 IFS가 포함되어 있으면 어떻게됩니까? 그런 다음 다른 것을 선택해야합니다. 그러나, 만약 그렇다면 ...
추후 공지가있을 때까지 일시 중지되었습니다.

3
/POSIX 및 :DOS 파일 시스템에서 선택할 수 있습니다 . 다른 파일 시스템에 대해 IFS에 대해 선택할 수있는 잘못된 문자가 있습니다. 더 복잡하고 펄을 사용하는 것이 좋습니다.
Darren Hall

2
/를 사용하는 문제는 디렉토리 구분 기호이며 find슬래시를 포함하는 경로가있는 파일 이름을 반환 한다는 것 입니다. 스크립트에서 세미콜론을 슬래시로 변경하면 에코가 디렉토리와 파일 이름을 별도의 줄에 인쇄합니다.
추후 공지가있을 때까지 일시 중지되었습니다.

그것은 또한 매우 유용하게 보입니다. 파이프 while옵션으로 갔지만 이것도 꽤 효과적입니다. 예, 비슷한 구조에서 나중에 더 파싱해야했습니다. (입력 파일 이름은 .ogg이며 filesrcgst 파이프 라인에서와 같이 전달 되지만 출력 디렉토리에서 .mp3로 끝나는 파일이 생성되어 파이프 라인으로 전달됩니다. filesink물론이 과정을 수행해야합니다. 각 파일마다, 일부 echo는 사용자에게 제공됩니다.)
Samuel Jaeschke

4

루프 본문이 단일 명령 이상인 경우 xargs 를 사용 하여 쉘 스크립트를 구동 할 수 있습니다 .

export OUTPATH=/some/where/else/
find . -type d -print0 | xargs -0 bash -c 'for DIR in "$@"; do
  printf "mkdir -p %q\\n" "${OUTPATH}${DIR}"        # Using echo for debug; working script will simply execute mkdir
  echo Created $DIR
done' -

쉘이 Bourne / POSIX 종류이면 쉘 뒤에 대시 (또는 다른 '단어')를 포함 시키십시오 (쉘 스크립트에서 $ 0을 설정하는 데 사용됨). 또한 쉘 스크립트가 프롬프트에서 직접 인용되는 대신 인용 된 문자열 안에 작성되므로 인용에주의를 기울여야합니다.


또 다른 흥미로운 개념. 고마워-나중에 이것에 대한 용도를 찾게 될 것입니다 :)
Samuel Jaeschke

1

업데이트 된 질문에

mkdir -p \"${OUTPATH}${DIR}\"

이것은해야한다

mkdir -p "${OUTPATH}${DIR}"

감사. 결정된. 또한 DIR 대신 copyfile을 읽고있었습니다 : copy-paste : P
Samuel Jaeschke

1
find . -type d -exec mkdir -p "{}\040" ';' -exec echo "Created {}\040" ';'

0

또는 전체를 훨씬 덜 복잡하게 만들려면

% rsync -av --include='*/' --exclude='*' SRC DST

이것은 SRC의 디렉토리 구조를 DST로 복제합니다.


아니요, 반복 구조가 필요하므로 각 파일에 대해 여러 줄의 코드를 실행할 수 있습니다. "이제 다음과 유사한 구조로 각 파일에서 gstreamer를 포함하는 더 복잡한 명령을 실행하고 싶기 때문에 이와 같이 반복 할 수있는 방법이 필요합니다." 확실하지 않으면 죄송합니다.
Samuel Jaeschke

내가 준 명령은 당신이 요구 한 문제를 해결합니다. 이것이 당신 쪽의 더 큰 '파이프 라인'의 일부인지는 중요하지 않습니다. 질문에 설명 된 것처럼 문제가있는 다른 사람에게는 rsync-approach가 작동합니다. 잠재적 인 불확실성에 대해 미안할 필요가 없습니다. :)
akira

네. 아니요, 나중에 비슷한 while... do... done구조를 사용하여 찾기에서 비슷한 처리를 수행 한다는 것을 의미 합니다. 각 파일에서 여러 줄의 코드를 실행해야합니다 (문자열, 에코, gst- 런치 등). )을 rsync달성하지 못합니다. 그렇기 때문에 비슷한 구조 내에서 더 복잡한 명령 집합을 실행할 수 있어야한다고 지정했습니다. 내 스크립트는이 루프 구조를 두 번 사용하므로 질문이 중간에 덜 크지 않은 것을 게시했습니다.
Samuel Jaeschke

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.