bash: 공백이 있는 파일 이동

bash: 공백이 있는 파일 이동

파일 이름에 공백이 있는 단일 파일을 이동하면 다음과 같이 작동합니다.

$ mv "file with spaces.txt" "new_place/file with spaces.txt"

이제 공백이 포함될 수 있는 파일 목록이 있고 이를 이동하고 싶습니다. 예를 들어:

$ echo "file with spaces.txt" > file_list.txt
$ for file in $(cat file_list.txt); do mv "$file" "new_place/$file"; done;

mv: cannot stat 'file': No such file or directory
mv: cannot stat 'with': No such file or directory
mv: cannot stat 'spaces.txt': No such file or directory

첫 번째 예는 작동하지만 두 번째 예는 작동하지 않는 이유는 무엇입니까? 어떻게 작동하게 할 수 있나요?

답변1

항상, 항상 사용하세요for foo in $(cat bar). 이는 일반적으로 알려진 오류입니다.배쉬 트랩 번호 1. 다음을 사용해야 합니다.

while IFS= read -r file; do mv -- "$file" "new_place/$file"; done < file_list.txt

for루프를 실행하면 bash는 읽는 내용에 토큰화를 적용합니다. 즉 , , 및 다음 a strange blue cloud으로 읽혀집니다 .astrangebluecloud

$ cat files 
a strange blue cloud.txt
$ for file in $(cat files); do echo "$file"; done
a
strange
blue
cloud.txt

비교:

$ while IFS= read -r file; do echo "$file"; done < files 
a strange blue cloud.txt

아니면 심지어 고집한다면우루크:

$ cat files | while IFS= read -r file; do echo "$file"; done
a strange blue cloud.txt

따라서 while루프는 입력을 읽고 를 read사용하여 각 행을 변수에 할당합니다. IFS=입력 필드 구분 기호를 NULL * 로 설정하면 이 -r옵션은 read백슬래시 이스케이프를 해석하지 못하게 합니다(그래서 탭 대신 \t슬래시+로 처리 됩니다). tafter는 "--뒤의 모든 것을 옵션이 아닌 인수로 처리"하는 것을 --의미하며 , 이를 통해 올바른 파일 이름으로 시작하는 파일 이름을 적절하게 처리할 수 있습니다.mv-


* 엄밀히 말하면 여기서는 필요하지 않습니다. 이 경우 유일한 이점은 read선행 또는 후행 공백을 제거하지 않는 것입니다. 그러나 개행 문자가 포함된 파일 이름을 처리해야 하거나 일반적으로 임의의 파일 이름을 처리할 수 있습니다.

답변2

$(cat file_list.txt)POSIX 셸에서 목록 컨텍스트에서와 같이 따옴표가 없는 것은 bash분할+글로브 연산자입니다 zsh(만나뉘다섹션에서 예상한 대로).

( $IFS기본적으로프로그램 제어, TAB 및 NL) 및 globbing을 완전히 끄지 않으면 globbing이 발생합니다.

여기서는 개행 문자로만 분할하고 전역 부분은 필요하지 않으므로 다음과 같아야 합니다.

IFS='
' # split on newline only
set -o noglob # disable globbing

for file in $(cat file_list.txt); do # split+glob
  mv -- "$file" "new_place/$file"
done

while readmv이는 또한 빈 줄을 삭제하고, 종료되지 않은 후행 줄을 유지하고, stdin을 유지하는(예를 들어 메시지가 표시되는 경우 필수) 장점이 있습니다(루핑과 반대) .

파일의 전체 내용을 메모리에 저장해야 한다는 단점이 있습니다( bash및 같은 쉘을 여러 번 사용 zsh).

일부 쉘의 경우( ksh그리고 zsh그 정도는 적지만 bash) $(<file_list.txt)대신 을 사용하여 최적화 할 수 있습니다 $(cat file_list.txt).

loop 를 사용하여 동일한 작업을 수행하려면 while read다음이 필요합니다.

while IFS= read <&3 -r file || [ -n "$file" ]; do
  {
    [ -n "$file" ] || mv -- "$file" "new_place/$file"
  } 3<&-
done 3< file_list.txt

또는 다음을 사용하여 bash:

readarray -t files < file_list.txt &&
for file in "${files[@]}"
  [ -n "$file" ] || mv -- "$file" "new_place/$file"
done

또는 다음을 사용하여 zsh:

for file in ${(f)"$(<file_list.txt)"}
  mv -- "$file" "new_place/$file"
done

또는 GNU를 사용 mv하고 다음을 수행하십시오 zsh.

mv -t -- new_place ${(f)"$(<file_list.txt)"}

또는 ksh/zsh/bash와 함께 GNU mv및 GNU를 사용하십시오.xargs

xargs -rd '\n' -a <(grep . file_list.txt) mv -t -- new_place

인용되지 않은 확장의 의미에 대한 자세한 내용은 다음을 참조하세요.bash/POSIX 쉘에서 변수를 인용하는 것을 잊어버리는 보안 위험

답변3

스크립팅 대신 find..를 사용할 수 있습니다.

 find -type f -iname \*.txt -print0 | xargs -IF -0 mv F /folder/to/destination/

파일이 파일에 있는 경우 다음을 수행할 수 있습니다.

cat file_with_spaces.txt | xargs -IF -0 mv F /folder/to/destination

그런데 두 번째는 잘 모르겠네요...

행운을 빌어요

관련 정보