다음 파일 이름이 주어지면:
$ ls -1
file
file name
otherfile
bash
그 자체는 포함된 공백에 대해 매우 잘 작동합니다.
$ for file in *; do echo "$file"; done
file
file name
otherfile
$ select file in *; do echo "$file"; done
1) file
2) file name
3) otherfile
#?
그러나 모든 파일을 처리하고 싶지 않거나 엄격 $PWD
하게 find
. 명목상 공백도 처리합니다.
$ find -type f -name file\*
./file
./file name
./directory/file
./directory/file name
whspace 안전 버전을 작성하려고 합니다.이것스크립틀릿은 출력을 가져와 find
다음으로 렌더링합니다 select
.
$ select file in $(find -type f -name file); do echo $file; break; done
1) ./file
2) ./directory/file
그러나 이렇게 하면 파일 이름에 공백이 생깁니다.
$ select file in $(find -type f -name file\*); do echo $file; break; done
1) ./file 3) name 5) ./directory/file
2) ./file 4) ./directory/file 6) name
보통 나는 이 문제를 주변을 어지럽히는 것으로 해결합니다 IFS
. 하지만:
$ IFS=$'\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
$ IFS='\n' select file in $(find -type f -name file\*); do echo $file; break; done
-bash: syntax error near unexpected token `do'
이 문제에 대한 해결책은 무엇입니까?
답변1
포함된 개행 문자가 아닌 공백과 탭만 처리해야 하는 경우 다음과 같이 mapfile
(또는 해당 동의어 )를 사용하여 배열을 읽을 수 있습니다.readarray
$ ls -1
file
other file
somefile
그 다음에
$ IFS= mapfile -t files < <(find . -type f)
$ select f in "${files[@]}"; do ls "$f"; break; done
1) ./file
2) ./somefile
3) ./other file
#? 3
./other file
만약 너라면하다줄 바꿈을 처리해야 하고 버전이 null로 구분된 1 을bash
제공 하는 경우 이를 로 수정할 수 있습니다 . 그렇지 않으면 루프를 사용하여 null로 구분된 출력에서 해당 배열을 어셈블합니다 .mapfile
IFS= mapfile -t -d '' files < <(find . -type f -print0)
find
read
$ touch $'filename\nwith\nnewlines'
$
$ files=()
$ while IFS= read -r -d '' f; do files+=("$f"); done < <(find . -type f -print0)
$
$ select f in "${files[@]}"; do ls "$f"; break; done
1) ./file
2) ./somefile
3) ./other file
4) ./filename
with
newlines
#? 4
./filename?with?newlines
1이 옵션은 -d
버전 4.4 iirc mapfile
에서 추가 되었습니다.bash
답변2
이 답변에는 해결책이 있습니다어느파일 유형. 개행 또는 공백이 포함됩니다.
최신 bash는 물론 고대 bash 및 오래된 posix 쉘에 대한 솔루션도 있습니다.
아래 이 답변 [1] 에 나열된 트리가 테스트에 사용되었습니다.
선택하다
select
배열을 사용하는 것은 쉽습니다:
$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done
또는 위치 매개변수를 사용하세요.
$ set -- "$dir"/*
$ select var; do echo "$var"; break; done
따라서 유일한 실제 문제는 배열 내부 또는 위치 매개변수 내에서 "파일 목록"(제대로 구분된)을 가져오는 것입니다. 계속 읽으십시오.
세게 때리다
bash로 보고한 문제를 볼 수 없습니다. Bash는 특정 디렉터리 내에서 검색할 수 있습니다.
$ dir='deep/inside/a/dir'
$ printf '<%s>\n' "$dir"/*
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
또는 루프를 선호하는 경우:
$ set -- "$dir"/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
위의 구문은 모든 (합리적인) 쉘(적어도 csh는 아님)에서 올바르게 실행됩니다.
위 구문의 유일한 제한은 다른 디렉터리를 입력하는 것입니다.
하지만 Bash는 이것을 할 수 있습니다:
$ shopt -s globstar
$ set -- "$dir"/**/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
특정 파일만 선택하세요(예:끝파일에서) *를 바꾸세요:
$ set -- "$dir"/**/*file
$ printf '<%s>\n' "$@"
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/zz last file>
강한
"space-안전한"제목에서 말씀하신 것 같군요."강한".
공백(또는 개행)에 강인한 가장 간단한 방법은 공백(또는 개행)이 포함된 입력 처리를 거부하는 것입니다. 쉘에서 이를 수행하는 매우 간단한 방법은 파일 이름이 공백으로 확장되면 오류와 함께 종료하는 것입니다. 이를 수행하는 방법은 여러 가지가 있지만 가장 간단한(및 POSIX) 방법은 다음과 같습니다(그러나 suddirectories 이름을 포함하고 도트 파일을 피하는 하나의 디렉토리 콘텐츠로 제한됨).
$ set -- "$dir"/file* # read the directory
$ a="$(printf '%s' "$@" x)" # make it a long string
$ [ "$a" = "${a%% *}" ] || echo "exit on space" # if $a has an space.
$ nl='
' # define a new line in the usual posix way.
$ [ "$a" = "${a%%"$nl"*}" ] || echo "exit on newline" # if $a has a newline.
사용된 용액이 이러한 항목 중 하나라도 신뢰할 수 있는 경우 테스트를 제거하십시오.
Bash에서는 위에서 설명한 **를 사용하여 하위 디렉터리를 즉시 테스트할 수 있습니다.
도트 파일을 포함하는 방법에는 여러 가지가 있으며 Posix 솔루션은 다음과 같습니다.
set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*
찾다
어떤 이유로 find를 사용해야 하는 경우 구분 기호를 NUL(0x00)로 바꾸세요.
배쉬 4.4+
$ readarray -t -d '' arr < <(find "$dir" -type f -name file\* -print0)
$ printf '<%s>\n' "${arr[@]}"
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/file>
배쉬 2.05+
i=1 # lets start on 1 so it works also in zsh.
while IFS='' read -d '' val; do
arr[i++]="$val";
done < <(find "$dir" -type f -name \*file -print0)
printf '<%s>\n' "${arr[@]}"
POSIXLY
find에 NUL 구분 기호가 없고 읽을 수 없는 -d
(또는 ) 효율적인 POSIX 솔루션을 공식화하려면 완전히 다른 접근 방식이 필요합니다.-a
-exec
쉘을 호출하려면 찾기 콤플렉스를 사용해야 합니다 :
find "$dir" -type f -exec sh -c '
for f do
echo "<$f>"
done
' sh {} +
또는 필요한 것이 select인 경우(select는 sh가 아닌 bash의 일부입니다):
$ find "$dir" -type f -exec bash -c '
select f; do echo "<$f>"; break; done ' bash {} +
1) deep/inside/a/dir/file name
2) deep/inside/a/dir/zz last file
3) deep/inside/a/dir/file with a
newline
4) deep/inside/a/dir/directory/file name
5) deep/inside/a/dir/directory/zz last file
6) deep/inside/a/dir/directory/file with a
newline
7) deep/inside/a/dir/directory/file
8) deep/inside/a/dir/file
#? 3
<deep/inside/a/dir/file with a
newline>
[1] 이 트리(\012는 개행 문자입니다):
$ tree
.
└── deep
└── inside
└── a
└── dir
├── directory
│ ├── file
│ ├── file name
│ └── file with a \012newline
├── file
├── file name
├── otherfile
├── with a\012newline
└── zz last file
다음 두 명령으로 빌드할 수 있습니다.
$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}
답변3
루프 구조 앞에는 변수를 설정할 수 없지만 조건 앞에는 설정할 수 있습니다. 다음은 매뉴얼 페이지의 일부입니다.
어떤 환경간단한 명령또는 위의 매개변수에 설명된 대로 매개변수 할당을 선행하여 기능을 일시적으로 향상시킬 수 있습니다.
(루프는 그렇지 않습니다.간단한 명령.)
다음은 실패 및 성공 시나리오를 보여주는 일반적인 구성입니다.
IFS=$'\n' while read -r x; do ...; done </tmp/file # Failure
while IFS=$'\n' read -r x; do ...; done </tmp/file # Success
불행하게도 관련 처리에 영향을 미치면서 변경 사항을 구성 IFS
에 포함시키는 방법은 없습니다 . 그러나 루프 외부에서 설정되는 것을 방지할 수 있는 것은 없습니다 .select
$(...)
IFS
IFS=$'\n'; while read -r x; do ...; done </tmp/file # Also success
다음 구조를 사용할 수 있다는 것을 알 수 있습니다 select
.
IFS=$'\n'; select file in $(find -type f -name 'file*'); do echo "$file"; break; done
방어 코드를 작성할 때 이 절을 서브셸에서 실행하거나 블록 주위에 저장 및 복원하는 것이 좋습니다 IFS
.SHELLOPTS
OIFS="$IFS" IFS=$'\n' # Split on newline only
OSHELLOPTS="$SHELLOPTS"; set -o noglob # Wildcards must not expand twice
select file in $(find -type f -name 'file*'); do echo $file; break; done
IFS="$OIFS"
[[ "$OSHELLOPTS" !~ noglob ]] && set +o noglob
답변4
나는 아마도 내 관할권 밖에 있을 것이지만 적어도 공백 문제가 없는 다음과 같은 것으로 시작할 수 있을 것입니다.
find -maxdepth 1 -type f -printf '%f\000' | {
while read -d $'\000'; do
echo "$REPLY"
echo
done
}
주석에 언급된 대로 잠재적으로 잘못된 가정을 피하려면 위 코드는 다음과 동일합니다.
find -maxdepth 1 -type f -printf '%f\0' | {
while read -d ''; do
echo "$REPLY"
echo
done
}