現在のディレクトリと特定の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
またはなどの標準のUnixユーティリティを使用する場合はさらにそうです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
文字など)が含まれる可能性がある場合(ここではいUnixファイル名の有効な文字)の代わりに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 //
.//
ところで、これは良いシェルプログラミングの練習ですいつも問題にならないと思っても、ファイル名に改行やその他の問題がある文字(スペース、タブ、セミコロン、アンパサンドなど)が表示される可能性を考慮してください。これが変数を使用するときに常に二重引用符を使用する理由の1つです。これは、ファイル名の区切り文字としてNULを使用することがLFを使用するよりも優れており、より信頼性が高く安全な理由です。
改行の代わりにNULを区切り文字として使用する理由を説明すると、追加のスコアが得られます。
修正する
代わりに、2つのforループを使用する必要がある場合でも、find
独自のパターンマッチングを実行しないでください。あなたのコードは、ファイルを一致させるためにglobを使用せず、カスタムパターン一致コードを使用します。それは同じではなく、近いこともありません。
以下は、実際にglobを使用して一致するファイル数を数える2つのforループを使用する例です。説明のために各ループの下に説明を追加しましたが、スクリプトでは1ループずつ実行するだけです。
現在のディレクトリのループ1:
for f in $myglob; do
[ -f "$f" ] && let numFile++
done
このfor
ループはまれなケースの1つの例です。いいえ$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
は本物指定されたパス名がシンボリックリンクの場合、ここで使用されているテストの組み合わせは実際の一般ファイルのみを計算し、一般ファイルへのシンボリックリンクは計算しないことを保証します。