空の場合を除き、bash反復ファイルリスト


33

これは簡単だと思っていましたが、予想よりも複雑であることが判明しています。

ディレクトリ内の特定の種類のすべてのファイルを反復処理するため、次のように記述します。

#!/bin/bash

for fname in *.zip ; do
   echo current file is ${fname}
done

これは、ディレクトリに一致するファイル少なくとも1つある限り機能します。しかし、一致するファイルがない場合、私はこれを取得します:

current file is *.zip

次に試しました:

#!/bin/bash

FILES=`ls *.zip`
for fname in "${FILES}" ; do
    echo current file is ${fname}
done

ファイルがない場合、ループの本体は実行されませんが、lsからエラーが発生します。

ls: *.zip: No such file or directory

一致するファイルをまったく処理しないループを作成するにはどうすればよいですか?


7
shopt -s nullglobforループを実行する前に追加します。
cuonglm

@cuolnglm:このRHEL5ボックスの空のリストではなく、実行中のスクリプトの名前を返すLSでspookilyこの結果(bashの3.2.25)私が行った場合FILES=のls * .zipファイル; for fname in "${FILES}"...が、と期待どおりに作業を行うfor fname in *.zip ; do....
symcbean

3
for file in *.zipではなくを使用し`ls ...`ます。@cuonglmの提案では*.zip、パターンがどのファイルにも一致しない場合は何も展開されません。ls引数なしでは、現在のディレクトリがリストされます。
ステファンシャゼル

1
この質問でlsは、一般に出力の解析を避ける理由について説明します。なぜ解析しないのlsですか?; そのページの上部近くにあるBashGuideのParsingLsの記事へのリンクも参照してください。
PM 2Ring

回答:


34

ではbashnullglob何も一致しないパターンがリテラル文字列として扱われるのではなく、「消える」ようにオプションを設定できます。

shopt -s nullglob
for fname in *.zip ; do
   echo "current file is ${fname}"
done

POSIXシェルスクリプトでは、fname存在することを確認します(同時に[ -f ]、それが通常のファイル(または通常のファイルへのシンボリックリンク)であり、directory / fifo / device ...などの他のタイプではないことを確認します):

for fname in *.zip; do
    [ -f "$fname" ] || continue
    printf '%s\n' "current file is $fname"
done

タイプに関係なく名前が終わるすべての(隠されていない)ファイルをループ[ -f "$fname" ]する[ -e "$fname" ] || [ -L "$fname ]場合は、と置き換えます.zip

交換する*.zip.*.zip .zip *.zipあなたもその名を終了するに隠されたファイルを考慮したい場合.zip


1
shopt -s nullglobUbuntu 17.04では動作しませんでしたが、[ -f "$fname" ] || continueうまくいきました。
koppor

@koppor実際にはを使用していないようですbash
chepner

2
set ./*                               #set the arg array to glob results
${2+":"} [ -e "$1" ] &&               #if more than one result skip the stat "$1"
printf "current file is %s\n" "$@"    #print the whole array at once

###or###

${2+":"} [ -e "$1" ] &&               #same kind of test
for    fname                          #iterate singly on $fname var for array
do     printf "file is %s\n" "$fname" #print each $fname for each iteration
done                                  

ここのコメントでは、関数の呼び出しに言及しています...

file_fn()
    if     [ -e "$1" ] ||               #check if first argument exists
           [ -L "$1" ]                  #or else if it is at least a broken link
    then   for  f                       #if so iterate on "$f"
           do : something w/ "$f"
           done
    else   command <"${1-/dev/null}"    #only fail w/ error if at least one arg
    fi

 file_fn *

2

検索を使用

export -f myshellfunc
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec bash -c 'myshellfunc "$0"' {} \;

export -fこれを機能させるには、シェル関数をエクスポートする必要があります。今すぐfind実行bashシェルの機能を実行し、唯一の現在のディレクトリレベルのままです。


これはサブディレクトリを再帰的に処理するため、一致するスクリプト(スクリプトではなく)を呼び出したいと思います。
symcbean

@symcbean単一のディレクトリに制限し、bash関数を処理するように編集しました
Dani_l

-3

の代わりに:

FILES=`ls *.zip`

試してください:

FILES=`ls * | grep *.zip`

このように、lsが失敗した場合(これはあなたの場合)、失敗した出力をgrepし、空の変数として返します。

current file is      <---Blank Here

これにロジックを追加して、「ファイルが見つかりません」を返すようにすることができます

#!/bin/bash

FILES=`ls * | grep *.zip`
if [[ $? == "0" ]]; then
    for fname in "$FILES" ; do
        echo current file is $fname
    done
else
    echo "No Files Found"
fi

この方法は、前のコマンドが成功した場合(値0で終了)、現在のファイルを印刷します。そうでない場合は、「ファイルが見つかりません」と印刷します。


1
私は(1つの以上のプロセスを追加することは悪いアイデアだと思いますgrep)のではなく、より良いツールを(使用して問題を解決しようとするfind)または(と、現在のソリューションのために、関連する設定を変更shopt -s nullglob
トーマスBaruchel

1
元の投稿に対するOPのコメントによると、これはshopt -s nullglob機能しません。私はfind自分の答えを確認しながら試しましたが、失敗し続けました。ダニが言った輸出のせいだと思う。
キップK
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.