これを行うための清潔で伝統的な方法はありますか?
たとえば、特定のディレクトリに「.gz」ファイルがある場合は、解凍したいと思います。しかし、そうでなければ、何のエラーも見たくありません。
を使用するとgzip -d /mydir/*.gz
エラーが発生します。
gzip: /mydir/*.gz: No such file or directory
shopt -s nullglob
まず、次のようにするとgzip -d /mydir/*.gz
次のような結果が得られます。
gzip: compressed data not read from a terminal. Use -f to force decompression.
For help, type: gzip -h
私の知る方法がうまくいくことがわかっており、これを答えとして投稿します。より良い、きれいな方法があるかどうか疑問に思います。
POSIXの互換性は利点ですが、必須ではありません。
答え1
そしてbash
:
shopt -s nullglob
files=(/mydir/*.gz)
((${#files[@]} == 0)) || gzip -d -- "${files[@]}"
そしてzsh
:
files=(/mydir/*.gz(N))
(($#files == 0)) || gzip -d -- $files
zsh
、なしでBourneシェルより前の(N)
cshまたはtcshと同様に、globが一致しないとコマンドは実行されません。エラーメッセージを避けるために上記の操作を実行できます(一致するものはありません。Bourneのようなシェルgzip
の場合、拡張グローブで失敗するのとは対照的に)。 with を使用すると、bash
同じ結果が得られます。bash
shopt -s failglob
でzsh
失敗したglobは、拡張シェルプロセスが非対話式で終了することを可能にする致命的なエラーです。この場合、サブシェルを使用するか、エラートラップメカニズムを使用して(またはもちろん推奨されていない設定(たとえば)やオプションを使用して)スクリプトが終了するのzsh
を防ぐことができます。{ try-block; } always { error-catching; }
nonomatch
sh
nullglob
noglob
$ zsh -c 'echo zz*; echo not output'
zsh:1: no matches found: zz*
$ zsh -c '(echo zz*); echo output'
zsh:1: no matches found: zz*
output
$ zsh -c '{echo zz*;} always {TRY_BLOCK_ERROR=0;}; echo output'
zsh:1: no matches found: zz*
output
$ zsh -o nonomatch -c 'echo zz*; echo output'
zz*
output
globを外部コマンドに渡すと、コマンドを実行するために分岐されたプロセスのみが終了します。
$ zsh -c '/bin/echo zz*; echo still output and the previous command exited with $?'
zsh:1: no matches found: zz*
still output and the previous command exited with 1
ksh93による
ksh93
zsh
(N)
グローバルにオプションを設定する必要がないように、glob修飾子と同様のメカニズムが最終的に追加されましたnullglob
。
files=(~(N)/mydir/*.gz)
((${#files[@]} == 0)) || gzip -d -- "${files[@]}"
POSIXとして
一致しないglobが拡張されずに渡されるPOSIXでは、その動作をsh
無効にする方法はありません(唯一のPOSIX glob関連オプションはnoglob
globbingを完全に無効にすることです)。コツは次のことです。
set -- /mydir/[*].gz /mydir/*.gz
case $#$1$2 in
'2/mydir/[*].gz/mydir/*.gz') : no match;;
*) shift; gzip -d -- "$@"
esac
/mydir/*.gz
一致するものがなければ、それ自体が拡張されるというアイデアです(/mydir/*.gz
)。ただし、実際に呼び出されるファイルがある場合でも拡張できるため、2つのケースを区別/mydir/*.gz
するためにglobを使用します。 glob/mydir/[*].gz
は、そのように呼び出されるファイルがある場合でも拡張されます。/mydir/*.gz
find
これはぎこちないので、次の場合に使用することをお勧めします。
find /mydir/. ! -name . -prune ! -name '.*' \
-name '*.gz' -type f -exec gzip -d {} +
これは! -name . -prune
サブディレクトリを調べません(一部のfind
実装には同等の機能があります-mindepth 1 -maxdepth 1
)。! -name '.*'
globと同様に、隠しファイルを除外します。
find
もう1つの利点は、ファイルリストが大きすぎて実行されたコマンドのパラメータサイズ制限に合わない場合でもまだ機能することです(複数のコマンドが実行される状況を回避する必要がある場合は、withとwithにもメカニズムがあります。この問題を解決するため)。gzip
ksh93
command -x
zsh
zargs
find
もう1つの利点は、ファイルの内容を読み取れない/mydir
場合やファイルの種類を確認できない場合にエラーメッセージが表示されることです(globは自動的に問題を無視し、そのファイルが存在しないかのように動作します)。
gzip
小さな欠点は、終了状態の正確な値を失うことです(2つの呼び出しのいずれかがゼロ以外の終了gzip
状態で失敗すると、find
まだゼロ以外の終了状態で終了します(必ずしも同じではありません)。したがって、ほとんどの場合、これで十分です)。
もう1つの利点は、.glob-type f
の名前を解凍しようとすることを避けるために追加できることです(通常のファイルのみ)。以下を行う必要があります。.gz
zsh
*.gz(.)
set --
for f in /mydir/*.gz
[ -f "$f" ] && [ ! -L "$f" ] && set -- "$@" "$f"
done
[ "$#" -eq 0 ] || gzip -d -- "$@"
答え2
1つの方法は次のとおりです。
shopt -s nullglob
for f in /mydir/*.gz; do
gzip -d /mydir/*.gz
break
done
for
nullglobに設定されたループは、globに拡張がある場合にのみループを実行し、無条件ステートメントはコマンドが一度だけ実行されることを保証しますbreak
。gzip
for
これはループをループとして使用するので少し面白いですが、うまくいきますif
。
答え3
最も簡単な解決策は
for f in *.gz; do
test -f "$f" && gzip -d -- "$f"
done
または
for f in *.gz; do
[ -f "$f" ] && gzip -d -- "$f"
done
でも
for f in *.gz; do
if [ -f "$f" ]; then
gzip -d -- "$f"
fi
done
コードの機能を説明するのに役立つ場合は、冗長に説明します。さまざまなシェルにはさまざまなタスクを実行するための簡潔な構文がありますが、実行中のタスクを文書化するという側面(および移植性)の点で上記の構文を超えていません。私は主にシェルスクリプトとあなたのスクリプトが他の人によって使用され修正される可能性について考えています。
編集する:
glob に拡張子があるかどうかを確認するには:
if [ "$(printf '%s' *.gz)" = "*.gz" ] && [ ! -f "*.gz" ]; then
# From Stéphane Chazelas: "There are no gzipped files that I
# can tell, or if there's one, it's called *.gz and it is not a
# regular file (after symlink resolution) or at least I can't tell
# if it's a regular file or not."
echo "There are no gzipped files"
else
echo "There are gzipped files"
fi
以下のエキゾチックなファイル名に関するStéphaneの追加説明も参照してください。