Linux에서는 파일 이름에 "슬래시"와 "널 문자" 두 문자만 금지됩니다. 따라서 모든 스크립트 언어에서 특별한 의미를 갖는 모든 문자는 이스케이프되어야 하지만 모든 이스케이프 시퀀스는 파일 이름에도 허용됩니다! 설상가상으로, bash의 일부 이스케이프 방법은 특정 문자만 이스케이프하므로 많은 수의 다른 문자를 이스케이프하려면 여러 가지 이스케이프 방법을 함께 사용해야 하지만 서로 간섭하게 됩니다! 설상가상으로 일부 명령은 목적을 달성하기 위해 특정 문자를 사용하고 다른 명령은 다른 문자를 사용하므로 파일에 대한 모든 간단한 작업에 대해 파일 이름을 다르게 이스케이프해야 합니다! 설상가상으로 파일 이름을 안전하게 구분하는 데 널 문자만 사용할 수 있지만 대부분의 명령은 널 문자와 함께 작동하지 않습니다. 설상가상으로, 기본적으로 Linux의 모든 것은 파일입니다. 따라서 이는 짜증스러울 뿐만 아니라 보안과 안정성의 문제이기도 합니다. Linux의 대부분은 스크립트 기반이므로 매우 결함이 많습니다!
그럼 제가 어디에서 잘못되고 있는지 알려주세요... 가능한 모든 파일 이름을 올바르게 처리하는 것이 가능합니까?
밝히다. 원래 나는 이렇게 생각했다.
특정 경로 아래의 파일 및 폴더 나열
주어진 기준(연령, 파일 모드 또는 크기)과 일치하는 콘텐츠 목록을 검색합니다.
- 일치하는 파일과 폴더를 카테고리(예: 영화)로 이동합니다. 테스트의 복잡성으로 인해 하나의 명령으로 이 작업을 수행하는 것이 불가능(또는 실용적)하므로 다른 명령 간에 파일 이름을 전달해야 합니다. Bash 와일드카드는 파일 이름에 공백이 있기 때문에 가장 먼저 삭제됩니다. 와일드카드는 항상 공백이 포함된 파일 이름을 목록의 두 요소로 분할합니다. 그런 다음 "찾기"를 사용해 보았습니다. 이것은 더 좋지만 훨씬 느리고 사용하기 어렵습니다.
파일 이름에 어떤 문자가 있을지 모르기 때문에 파일 이름을 이스케이프하는 데 특수 문자를 사용할 수 없습니다. 몇 가지 테스트를 거친 후 어떤 캐릭터가 등장하는지는 시간 문제라는 것을 알았습니다.
나는 다음과 같이 필터를 정의해 보았습니다.
audio_ext=(*.mp3 *.wav *.ogg *.mid *.mod *.stm *.s3m *.it *.wma *.669 *.ac3)
와일드카드를 사용하면 권한이 박탈되기 때문에 이 방법으로 여러 용도로 필터를 정의할 수 없다는 것을 금방 깨달았습니다. 그래서 와일드카드와 역사를 비활성화했습니다 set -fH
. 와일드카드가 없으면 수동으로 확장해야 합니다.
while IFS= read -r -d $'\0'; do list+=("$REPLY") done < <( find . -maxdepth 1 -mindepth 1 ${params[@]} -print0 2>/dev/null )
params
이런 배열은 어디에 있습니까 "-iname" "*.mp3" "-o" "-iname" "*.wav"
? 이는 파일 이름에 "("가 포함될 때까지 작동합니다. 조회에서는 잘못된 사용에 대한 오류를 반환합니다.
솔직히 말해서...저는 최근까지 15년 동안 이 작업을 위해 배치 스크립트를 사용해 왔습니다. 글쓰기 시간은 오후 1~2시 정도입니다. 단점과 파일 이름 문제가 있지만 !
일반적으로 작동합니다. 나는 거의 두 달 동안 bash로 작성하려고 노력해 왔습니다. 추악하고 복잡하며 버그로 가득 차 있고 제대로 작동하지 않는 것 같습니다.
답변1
단순한. 글로빙을 사용하여 원하는 파일을 선택하고 파일 이름이 포함된 변수를 참조합니다.
shopt -s nullglob
for file in ./*.txt; do
do_something_with "$file"
done
그게 다야.
자세한 내용은:
업데이트: 와일드카드는아니요당신이 보는 단어 분할 효과를 유발합니다. 변수를 참조할 수 없습니다.
다음과 같은 방법으로 조건에 맞는 파일 정보를 얻을 수 있습니다.stat
read size mtime < <(stat -c "%s %Y" "$file")
[[ $size -gt 1000 ]] && echo "too big"
[[ $mtime -lt $(date -d yesterday +%s) ]] && echo "too old"
업데이트 2: 많은 특수 문자가 포함된 파일 이름을 만들려면 다양한 인용 메커니즘을 혼합해야 하지만 파일에 대한 모든 작업을 수행하는 것은 여전히 가능합니다.
$ filename='~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'"'"$' \a\t\n\r\f'".txt"
# ^^ single quoted part ^^^^^^^^^^^^^^^^
# double quoted part ^^^
# ANSI-C quoted part ^^^^^^^^^^^^^^
$ echo "$filename"
~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'
.txt
$ printf "%q\n" "$filename"
$'~ASDFzxcv!@#$%^&*()_+[]\\{}|;:",.<>?`\' \a\t\n\r\f.txt'
$ date > "$filename"
$ cat "$filename"
Thu Apr 12 15:14:29 EDT 2018
$ ls -lt
total 3836
-rw-rw-r-- 1 jackman jackman 29 Apr 12 15:14 ~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`' ?????.txt
︙
$ ls -lt --show-control-chars
total 3836
-rw-rw-r-- 1 jackman jackman 29 Apr 12 15:14 ~ASDFzxcv!@#$%^&*()_+[]\{}|;:",.<>?`'
.txt
︙
출력이 ls
터미널이 아닌 다른 것(예: 파일 또는 파이프)으로 리디렉션되는 경우 --show-control-chars
기본적으로 이 스타일을 사용합니다. 를 실행하면 이를 확인할 수 있습니다 ls -lt | cat
.
ls
예를 들어 다른 표시 옵션이 있습니다.--quoting-style=WORD
답변2
\0
파일 이름에는 널 문자( )와 슬래시(경로 구분 기호)를 제외한 모든 문자를 사용할 수 있습니다 . 변수는 모든 데이터를 보유할 수 있습니다(대부분의 쉘에서 널 문자 제외). 올바르게 인용하면 파일 이름을 변수에 안전하게 저장하고 유틸리티와 함께 사용할 수 있습니다.
귀하의 요점과 관련하여 :
파일 세트(일반 파일 또는 디렉토리)를 반복하려면 다음과 같은 간단한 쉘 루프를 사용할 수 있습니다.
for name in ./*; do
# some code that uses "$name"
done
특정 기준을 사용하여 특정 파일을 선택할 때 파일을 반복하는 것이 find
더 나은 옵션입니다 . 예를 들어, 현재 디렉터리(또는 그 이하)에서 며칠보다 오래된 (최소 며칠 전에 수정된) N
모든 일반 파일을 선택하려면 다음을 수행하세요.N
find . -type f -mtime +N
마찬가지로 -size
크기를 기준으로 파일을 선택하고 -name
파일 이름을 와일드카드 패턴과 일치시키는 데 사용됩니다.
*.mov
예를 들어, 지난 주에 수정된 파일 이름과 일치하는 일반 파일을 선택하려면 다음을 수행하십시오 .
find . -type f -name '*.mov' -mtime -7
그래서 실제로는하다$HOME/Movies
이러한 파일을 디렉터리 로 이동하는 등 몇 가지 작업을 수행합니다 .
find . -type f -name '*.mov' -mtime -7 -exec mv {} "$HOME/Movies" ';'
{}
호출 시 파일의 경로 이름을 바꿉니다 mv
. 경로 이름에 대한 셸의 토큰화 또는 파일 이름 확장이 호출되지 않으므로 인용할 필요가 없습니다 {}
(그렇다면 아무 것도 변경되지 않습니다) .find
이에 대한 추가 개선 사항은 대상 디렉터리에서 파일 이름 충돌을 감지하는 것입니다. 이를 위해 명령줄에서 여러 파일 이름을 가져오는 짧은 도우미 스크립트를 사용합니다.
destdir="$HOME/Movies"
for name do
if [ -f "$destdir/${name##*/}" ]; then
printf "%s already exists in %s, not overwriting it!\n" "${name##*/}" "$destdir" >&2
else
mv "$name" "$destdir"
fi
done
또는 바로가기 형식으로:
destdir="$HOME/Movies"
for name do
[ -f "$destdir/${name##*/}" ] && printf "skipping %s\n" "$name" >&2 && continue
mv "$name" "$destdir"
done
다음 명령에 다음을 삽입하세요 find
.
find . -type f -name '*.mov' -mtime -7 -exec sh -c '
destdir="$HOME/Movies"
for name do
[ -f "$destdir/${name##*/}" ] && printf "skipping %s\n" "$name" >&2 && continue
mv "$name" "$destdir"
done' sh {} +
이 프로세스 동안 우리는 쉘이 현재 처리 중인 경로 이름이나 파일 이름에 대해 토큰화 또는 파일 이름 일치를 수행하는 것을 허용하지 않습니다.
더 많은 정보를 알고 싶다면: