공백이있는 파일 목록을 반복합니다.
파일 목록을 반복하고 싶습니다. 이 목록은 find
명령 의 결과 이므로 다음을 생각해 냈습니다.
getlist() {
for f in $(find . -iname "foo*")
do
echo "File found: $f"
# do something useful
done
}
파일 이름에 공백이있는 경우를 제외하고는 괜찮습니다.
$ ls
foo_bar_baz.txt
foo bar baz.txt
$ getlist
File found: foo_bar_baz.txt
File found: foo
File found: bar
File found: baz.txt
공간 분할을 피하려면 어떻게해야합니까?
단어 기반 반복을 라인 기반 반복으로 바꿀 수 있습니다.
find . -iname "foo*" | while read f
do
# ... loop body
done
이를 달성하기위한 몇 가지 실행 가능한 방법이 있습니다.
원래 버전에 가깝게 고정하려면 다음과 같이하십시오.
getlist() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: %s\n' "$file"
done
}
파일 이름에 리터럴 개행 문자가 있으면 여전히 실패하지만 공백은 끊지 않습니다.
그러나 IFS를 망칠 필요는 없습니다. 여기에 내가 선호하는 방법이 있습니다.
getlist() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
< <(command)
익숙하지 않은 구문 을 찾으면 프로세스 대체 에 대해 읽어야 합니다. 이 방법의 장점은 for file in $(find ...)
공백, 줄 바꿈 및 기타 문자가 포함 된 파일이 올바르게 처리된다는 것입니다. 때문에이 작품 find
과 -print0
용도 것 null
(일명를 \0
줄 바꿈과는 달리, 각 파일 이름에 대한 터미네이터로)과, 널 (null) 파일 이름에 법적 문자가 아닙니다.
거의 동등한 버전에 비해 이점
getlist() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: %s\n' "$file"
done
}
while 루프의 본문에 변수 할당이 유지된다는 것입니다. 즉, while
위와 같이 파이프 하면 몸체가 while
서브 쉘에 있으며 원하는 것이 아닐 수도 있습니다.
프로세스 대체 버전의 장점 find ... -print0 | xargs -0
은 최소입니다. xargs
파일을 한 줄로 인쇄하거나 단일 작업을 수행하는 것만으로도 버전이 양호하지만 여러 단계를 수행해야하는 경우 루프 버전이 더 쉽습니다.
편집 : 여기 에이 문제를 해결하기위한 다른 시도의 차이점에 대한 아이디어를 얻을 수있는 좋은 테스트 스크립트가 있습니다.
#!/usr/bin/env bash
dir=/tmp/getlist.test/
mkdir -p "$dir"
cd "$dir"
touch 'file not starting foo' foo foobar barfoo 'foo with spaces'\
'foo with'$'\n'newline 'foo with trailing whitespace '
# while with process substitution, null terminated, empty IFS
getlist0() {
while IFS= read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# while with process substitution, null terminated, default IFS
getlist1() {
while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done < <(find . -iname 'foo*' -print0)
}
# pipe to while, newline terminated
getlist2() {
find . -iname 'foo*' | while read -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# pipe to while, null terminated
getlist3() {
find . -iname 'foo*' -print0 | while read -d $'\0' -r file ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, default IFS
getlist4() {
for file in "$(find . -iname 'foo*')" ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# for loop over subshell results, newline terminated, newline IFS
getlist5() {
IFS=$'\n'
for file in $(find . -iname 'foo*') ; do
printf 'File found: '"'%s'"'\n' "$file"
done
}
# see how they run
for n in {0..5} ; do
printf '\n\ngetlist%d:\n' $n
eval getlist$n
done
rm -rf "$dir"
bash globbing에 의존하는 매우 간단한 해결책도 있습니다.
$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid file 3"
$ ls
stupid file 3 stupid file1 stupid file2
$ for file in *; do echo "file: '${file}'"; done
file: 'stupid file 3'
file: 'stupid file1'
file: 'stupid file2'
이 동작이 기본 동작인지 확신 할 수 없지만 내 shopt에 특별한 설정이 표시되지 않으므로 "안전"해야합니다 (osx 및 ubuntu에서 테스트 됨).
find . -iname "foo*" -print0 | xargs -L1 -0 echo "File found:"
find . -name "fo*" -print0 | xargs -0 ls -l
참조하십시오 man xargs
.
로 다른 유형의 필터링을 수행하지 않기 때문에 4.0 find
에서 다음을 사용할 수 있습니다 bash
.
shopt -s globstar
getlist() {
for f in **/foo*
do
echo "File found: $f"
# do something useful
done
}
The **/
will match zero or more directories, so the full pattern will match foo*
in the current directory or any subdirectories.
I really like for loops and array iteration, so I figure I will add this answer to the mix...
I also liked marchelbling's stupid file example. :)
$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid file 3"
Inside the test directory:
readarray -t arr <<< "`ls -A1`"
This adds each file listing line into a bash array named arr
with any trailing newline removed.
Let's say we want to give these files better names...
for i in ${!arr[@]}
do
newname=`echo "${arr[$i]}" | sed 's/stupid/smarter/; s/ */_/g'`;
mv "${arr[$i]}" "$newname"
done
${!arr[@]} expands to 0 1 2 so "${arr[$i]}" is the ith element of the array. The quotes around the variables are important to preserve the spaces.
The result is three renamed files:
$ ls -1
smarter_file1
smarter_file2
smarter_file_3
find
has an -exec
argument that loops over the find results and executes an arbitrary command. For example:
find . -iname "foo*" -exec echo "File found: {}" \;
Here {}
represents the found files, and wrapping it in ""
allows for the resultant shell command to deal with spaces in the file name.
In many cases you can replace that last \;
(which starts a new command) with a \+
, which will put multiple files in the one command (not necessarily all of them at once though, see man find
for more details).
In some cases, here if you just need to copy or move a list of files, you could pipe that list to awk as well.
Important the \"" "\"
around the field $0
(in short your files, one line-list = one file).
find . -iname "foo*" | awk '{print "mv \""$0"\" ./MyDir2" | "sh" }'
Ok - my first post on Stack Overflow!
Though my problems with this have always been in csh not bash the solution I present will, I'm sure, work in both. The issue is with the shell's interpretation of the "ls" returns. We can remove "ls" from the problem by simply using the shell expansion of the *
wildcard - but this gives a "no match" error if there are no files in the current (or specified folder) - to get around this we simply extend the expansion to include dot-files thus: * .*
- this will always yield results since the files . and .. will always be present. So in csh we can use this construct ...
foreach file (* .*)
echo $file
end
if you want to filter out the standard dot-files then that is easy enough ...
foreach file (* .*)
if ("$file" == .) continue
if ("file" == ..) continue
echo $file
end
The code in the first post on this thread would be written thus:-
getlist() {
for f in $(* .*)
do
echo "File found: $f"
# do something useful
done
}
Hope this helps!
Another soluce doing the job...
Goal was :
- select/filter filenames recursively in directories
- handle each names (whatever space in path...)
#!/bin/bash -e
## @Trick in order handle File with space in their path...
OLD_IFS=${IFS}
IFS=$'\n'
files=($(find ${INPUT_DIR} -type f -name "*.md"))
for filename in ${files[*]}
do
# do your stuff
# ....
done
IFS=${OLD_IFS}
참고URL : https://stackoverflow.com/questions/7039130/iterate-over-a-list-of-files-with-spaces
'Programing' 카테고리의 다른 글
Xcode 5에서 사용할 수있는 새로운 설명서 명령은 무엇입니까? (0) | 2020.05.16 |
---|---|
열에서 고유 한 값 선택 (0) | 2020.05.16 |
JavaScript를 사용하여 운영 체제 버전을 찾는 방법은 무엇입니까? (0) | 2020.05.16 |
.NET에 사용 가능한 읽기 전용 일반 사전이 있습니까? (0) | 2020.05.16 |
SQL : 레코드가 있는지 올바르게 확인하는 방법 (0) | 2020.05.16 |