有没有一种好的干净的传统方法可以做到这一点?
例如,如果某个目录中有任何“.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
、 without中(N)
,就像在 Bourne shell 之前的 csh 或 tcsh 中一样,如果 glob 不匹配,则不会运行该命令,您只需执行上述操作即可避免产生错误消息(未找到匹配项与在或其他类似 Bourne 的 shellgzip
的情况下在扩展的 glob 上失败相反)。您可以使用withbash
获得相同的结果。bash
shopt -s failglob
在 中zsh
,失败的 glob 是一个致命错误,会导致展开的 shell 进程(非交互时)退出。在这种情况下,您可以通过使用子 shell 或使用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
最终添加了类似于的glob 限定符的机制(N)
,以避免必须nullglob
全局设置选项:
files=(~(N)/mydir/*.gz)
((${#files[@]} == 0)) || gzip -d -- "${files[@]}"
POSIXly
可移植地,在 POSIX 中sh
,不匹配的 glob 未扩展地传递,无法禁用该行为(唯一的 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
)。然而,它也可以扩展到如果有一个文件实际被称为/mydir/*.gz
,因此为了区分这两种情况,我们还使用了 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
将运行多个命令, (with ) 和(with ) 也有机制解决这个问题)。gzip
ksh93
command -x
zsh
zargs
另一个好处是,如果find
无法读取文件的内容/mydir
或无法确定文件的类型,您将收到错误消息(glob 会默默地忽略问题并表现得好像相应的文件不存在一样)。
一个小的缺点是,您会丢失 的gzip
退出状态的确切值(如果任何一个gzip
调用以非零退出状态失败,find
仍然会以非零退出状态退出(尽管不一定相同),所以对于大多数用例来说这已经足够了)。
另一个好处是您可以添加-type f
以避免尝试解压缩名称以.gz
.除了zsh
(*.gz(.)
仅适用于常规文件)之外,glob 无法按文件类型进行过滤,您需要执行以下操作:
set --
for f in /mydir/*.gz
[ -f "$f" ] && [ ! -L "$f" ] && set -- "$@" "$f"
done
[ "$#" -eq 0 ] || gzip -d -- "$@"
答案2
一种方法是:
shopt -s nullglob
for f in /mydir/*.gz; do
gzip -d /mydir/*.gz
break
done
for
如果 glob 具有扩展,则设置为 nullglob 的循环将仅执行循环,并且无条件语句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
如果它有助于解释代码的作用,我完全赞成冗长的内容。各种 shell 都有紧凑的语法来完成各种事情,但在记录您正在做的事情(和可移植性)方面,它并没有打败上面的。我在这里主要考虑的是 shell 脚本,以及您的脚本可能被其他人使用和修改的情况。
编辑:
如果您只想查看 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 关于外来文件名的进一步评论。