파일 이름에 공백이 있는 단일 파일을 이동하면 다음과 같이 작동합니다.
$ 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
으로 읽혀집니다 .a
strange
blue
cloud
$ 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
슬래시+로 처리 됩니다). t
after는 "--뒤의 모든 것을 옵션이 아닌 인수로 처리"하는 것을 --
의미하며 , 이를 통해 올바른 파일 이름으로 시작하는 파일 이름을 적절하게 처리할 수 있습니다.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 read
mv
이는 또한 빈 줄을 삭제하고, 종료되지 않은 후행 줄을 유지하고, 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
그런데 두 번째는 잘 모르겠네요...
행운을 빌어요