현재 디렉터리와 특정 glob과 일치하는 모든 하위 디렉터리의 모든 파일 수를 계산하려고 합니다. 예를 들어 ".txt"로 끝나는 모든 파일을 찾습니다.
(현재 디렉터리의 모든 파일을 일치시키기 위해 for 루프를 사용해야 했고, 현재 디렉터리의 모든 하위 디렉터리를 반복하기 위해 또 다른 for 루프를 사용해야 했습니다)
#!/bin/bash
myglob="$1"
if [ $# -eq 1 ]; then
dir=$1
else
echo -n Please enter an ending file name:
read -r myglob
fi
# echo Directory $dir
numDir=0
numFile=0
for file in ./*; do
# if [ -d "$file" ]; then
# echo $file is a FIRST directory
# let numDir=numDir+1
if [[ "$file" == *"$myglob" ]]; then
echo $file is a FIRST file
let numFile++
fi
for file in ./*/*; do
if [[ "$file" == *"$myglob" ]]; then
echo $file is a SECOND file
let numFile++
fi
done
done
#echo "$dir" contains "$numDir" directories
echo "$dir" contains "$numFile" files
답변1
과제의 질문을 잘못 읽은 것 같습니다.
그것은 말한다"현재 디렉토리"
.
,지금 바로아니요~
또는~/linux2/q3
그것은 또한 말한다"및 모든 하위 디렉터리". 이것이 입문 쉘 스크립팅 과정인 것으로 생각하면, 그들이 하위 디렉토리를 반복하기 위해 bash에 자신의 코드를 작성하기를 원하지는 않을 것입니다. 그건아니요초보자의 작업.
이는 거의 확실하게 "
find
재귀적으로 하위 디렉토리에 대한 표준 기능을 사용하는 것"을 의미합니다.자체 파일 이름 패턴 일치를 구현하는 대신 glob을 사용하라는 메시지가 표시됩니다. 자신의 패턴 매칭 코드가 아무리 잘 작성되어 있어도아니요글로벌을 사용하세요.
find
-name
glob을 사용하여 파일을 일치시키는 옵션이 있습니다 ."파일 끝 일치"나 파일 확장자도 표시되지 않습니다. "특정 전역 일치"라고 말하고 ".txt"를 다음과 같이 제공합니다.예. 공할 수 있는파일 확장자와 일치하지만 그 이상을 일치시키는 데에도 사용할 수 있습니다.
"X를 실행하기 위한 쉘 스크립트 작성"(또는 유사한 단어)은 반드시 "외부 프로그램을 사용하지 않고 내장 명령만 사용하는 쉘 스크립트 작성"을 의미하지는 않습니다. 사실 이것은 확실히 의미하는 것은 아닙니다~하지 않는 한이것은 분명히 명시되어 있습니다.
작업을 수행하기 위해 외부 프로그램을 호출하는 것은 쉘 스크립트가 수행하는 작업이며 쉘 스크립트에서는 완전히 정상적이고 예상되는 것입니다. 특히
find
또는 와 같은 표준 유닉스 유틸리티를 사용할 때 더욱 그렇습니다wc
.wc
파일이나 표준 입력의 문자, 줄 및/또는 단어 수를 계산하는 데 사용할 수 있는 표준 프로그램입니다. 이 경우 행 수만 계산하려는 것이므로wc
's-l
옵션을 사용하십시오.
#!/bin/bash
# Count the number of files matching a glob in the current directory
# and all subdirectories.
#
# The glob can be specified on the command line, in which case it
# MUST be quoted or escaped to prevent the shell from expanding it.
# e.g. use '*.txt' or \*.txt, not just *.txt.
#
# if the glob is not specified on the command line, the script prompts
# for a glob until one is provided.
myglob="$1"
while [ -z "$myglob" ] ; do
read -p 'Enter a glob: ' myglob
done
numfiles=$(find . -type f -name "$myglob" | wc -l)
echo $numfiles
현재 디렉토리의 파일 이름에 개행 문자(예: LF
문자)가 포함될 가능성이 있는 경우(여기서예유닉스 파일 이름의 유효한 문자) 대신 NUL
파일 이름 구분 기호로 사용하십시오 LF
.
numfiles=$(find . -type f -name "$myglob" -print0 |
awk -v RS='\0' '{count++}; END {print count}')
대신 스크립트를 wc -l
사용하여 NUL로 구분된 파일 이름을 계산합니다.awk
또는 Stéphane Chazelas가 주석에서 지적했듯이 다음 find
을 사용하여 이 작업을 수행 할 수 있습니다 grep
.
numfiles=$(find .//. -type f -name "$myglob" | grep -c //)
시작 .//.
디렉터리 인수를 사용하면 find
출력 앞에 가 붙 습니다. 파일 이름에 표시 .//
할 수 없으므로 파일 개수를 계산하는 데 사용할 수 있습니다 . 파일 이름에 한 번만 발생하므로 파일 이름에 줄 바꿈이 있는지 여부에 관계없이 작동합니다.//
find
grep -c //
.//
그건 그렇고, 이것은 좋은 쉘 프로그래밍 연습입니다언제나문제가 되지 않는다고 생각되더라도 파일 이름에 줄 바꿈 및 기타 문제가 있는 문자(예: 공백, 탭, 세미콜론, 앰퍼샌드 등)가 나타날 가능성을 고려하십시오. 이것이 변수를 사용할 때 항상 큰따옴표를 사용해야 하는 이유 중 하나입니다. 이것이 파일 이름 구분 기호로 NUL을 사용하는 것이 LF를 사용하는 것보다 더 좋고, 더 안정적이고 안전한 이유입니다.
개행 대신 NUL을 구분 기호로 사용하는 이유를 설명하면 추가 점수를 얻을 수 있습니다.
고쳐 쓰다
대신 두 개의 for 루프를 사용해야 하는 경우에도 find
자체 패턴 일치를 수행하면 안 됩니다. 귀하의 코드는 파일을 일치시키기 위해 glob을 사용하지 않고 사용자 정의 패턴 일치 코드를 사용합니다. 그것은 같은 것이 아니며, 심지어 가깝지도 않습니다.
다음은 실제로 glob을 사용하여 일치하는 파일 수를 계산하는 두 개의 for 루프를 사용하는 예입니다. 설명을 위해 각 루프 아래에 설명을 추가했지만 스크립트에서는 한 루프씩 실행하기만 하면 됩니다.
현재 디렉터리에 대한 루프 1:
for f in $myglob; do
[ -f "$f" ] && let numFile++
done
이 for
루프는 드문 경우 중 하나의 예입니다.아니요$myglob
당신이 그것을 사용할 때 인용하고 싶기 때문에생각하다글로브를 확장하는 쉘.
거의 모든 경우에는 쉘이 명령줄에서 변수를 확장하는 것을 원하지 않으므로~ 해야 하다또한 이 스크립트와 관련이 없더라도 배열 변수를 확장하려는 경우에도 배열의 각 개별 요소를 A "로 처리하려고 하므로 "$myglob"
큰 따옴표로 묶어야 합니다. 단어".$myglob
"${array[@]}"
어쨌든 이것은 [ -f "$f" ]
"$f"가 존재하고 일반 파일인지 테스트하므로 디렉터리(또는 심볼릭 링크나 명명된 파이프(fifos라고도 함)와 같은 다른 항목)가 아닌 파일만 계산합니다. 이는 find
's 옵션을 사용하는 것과 동일한 효과를 갖습니다 -type f
.
./
파일 대신(또는 파일과 함께) 디렉터리 수를 계산하려면 다음을 사용할 수 있습니다.
[ -d "$f" ] && let numDir++
직접 하위 디렉터리에 대한 루프 2:
for f in */$myglob ; do
[ -f "$f" ] && let numFile++
done
*/$myglob
이는 단순히 반복하는 대신 반복한다는 점을 제외하면 첫 번째 for 루프와 거의 동일합니다 $myglob
.
대체로 다음과 같습니다.
#!/bin/bash
# comments deleted, same as version using find above.
myglob="$1"
while [ -z "$myglob" ] ; do
read -p 'Enter a glob: ' myglob
done
for f in $myglob; do
[ -f "$f" ] && let numFile++
done
for f in */$myglob ; do
[ -f "$f" ] && let numFile++
done
echo "$(pwd)/ and $(pwd)/*/ combined contain $numFile files matching '$myglob'"
versions 와 달리 find
이 루프는 현재 디렉터리와 그 바로 아래 디렉터리의 파일만 계산합니다. 하위 하위 디렉터리 등으로 더 깊게 반복되지 않습니다.
귀하의 질문을 읽고 제가 수집한 내용에 따르면 이것이 아마도 귀하가 원하는 것일 것입니다.
find
이 옵션을 사용하여 재귀 깊이를 제한할 수 있습니다 -maxdepth
. 예를 들어 find . -maxdepth 2 -type f -name "$myglob"
.
답변2
현재 디렉터리를 확장하고 일치하는 이름의 수를 계산하는 방법은 *.txt
다음과 같습니다.
set -- ./*.txt
이는 위치 매개변수( , 등)를 와일드카드 패턴과 일치하는 이름 $1
으로 설정합니다. shell 옵션이 shell 에 설정된 $2
경우 , 일치하는 항목이 없으면 빈 목록이 됩니다. 그렇지 않으면 목록에 확장되지 않은 패턴 자체가 포함됩니다. shell 옵션이 shell 에 설정되어 있으면 패턴과 일치하는 숨겨진 이름이 목록에 포함됩니다( 그렇지 않으면 숨겨진 이름이 일치하지 않습니다).nullglob
bash
dotglob
bash
*
위치 인수 목록의 길이는 입니다 $#
.
이것이 의미하는 바는 다음이 bash
현재 디렉터리에 일치하는 이름(아마도 숨겨진) 수를 계산하고 보고하는 짧은 스크립트라는 것입니다.*.txt
#!/bin/bash
shopt -s dotglob nullglob
set -- ./*.txt
printf 'There are %d names matching ./*.txt here\n' "$#"
globstar
쉘 옵션을 활성화하면 **
하위 디렉토리와 일치하는 에 액세스할 수 있습니다. 그런 다음 위 스크립트를 쉽게 확장하여 현재 디렉터리와 그 아래 디렉터리에서 재귀적으로 검색할 수 있습니다.
#!/bin/bash
shopt -s dotglob nullglob globstar
set -- ./**/*.txt
printf 'There are %d names matching ./**/*.txt here\n' "$#"
원하는 경우 일치하는 이름을 명명된 배열에 저장할 수 있습니다.
#!/bin/bash
shopt -s dotglob nullglob globstar
names=( ./**/*.txt )
printf 'There are %d names matching ./**/*.txt here\n' "${#names[@]}"
단일 열에 일치하는 이름을 인쇄하려면 다음을 수행하십시오.
printf '%s\n' "$@"
bash
또는 명명된 배열을 사용하는 경우
printf '%s\n' "${names[@]}"
일반 파일만 계산해야 한다면 분명히 glob과 일치하는 이름을 반복해야 합니다.
#!/bin/bash
shopt -s nullglob dotglob globstar
regular_files=()
for pathname in ./**/*.txt; do
if [ -f "$pathname" ] && [ ! -L "$pathname" ]; then
regular_files+=( "$pathname" )
fi
done
printf 'There are %d regular files matching ./**/*.txt\n' "${#regular_files[@]}"
위에서 사용한 테스트 -L
는진짜주어진 경로 이름이 심볼릭 링크인 경우 여기에 사용된 테스트 조합은 실제 일반 파일만 계산하고 일반 파일에 대한 심볼릭 링크는 계산하지 않는다는 것을 보장합니다.