我有一个要存档的目录列表。但有时它们并不全部存在。
我希望能够通过提供目录列表来创建存档,并且它只会存档现有的目录,并忽略丢失的目录。 (但如果不存在目录,仍然会失败)
这对我很有用,因为我正在运行一些持续集成,并且某些流程创建了某些我想在存档中保留以供将来使用的工件,我知道可以创建的所有可能路径,但并不总是确定哪些路径被创建。
假设可能的路径是:here_is_a_dir
here_is_another_one
yet_another_dir
我通常使用以下命令创建存档:
tar -czf archive.tgz here_is_a_dir here_is_another_one yet_another_dir
如果缺少任何目录,这当然会失败。
理想情况下,它应该是一个简单的命令,而不需要脚本来完成。 (具体来说,在我的环境中仅sh
可用,所以我不能使用任何像bash
其他外壳一样的花哨外壳,但这是特定于我的环境的,并且将来可能会改变,我认为使用其他外壳的答案可能也很好。 )
答案1
使用 的输出ls
通常是不明智和不安全的 - 请记住,空格和换行符以及其他 shell 元字符都是文件或目录名中的有效字符。在许多情况下可以解决这个问题,但是这样做通常比仅仅使用正确的工具来完成工作(即find
)要付出更多的努力。
所以,改用find
吧。例如:
find . -maxdepth 1 -type d \( -name here_is_a_dir -o -name here_is_another_one \
-o -name yet_another_dir \) -exec tar cfz archive.tgz {} +
-type d
这会在当前目录 ( ) 中找到任何匹配的目录 ( ).
并将它们用作tar
命令的参数。 to是一个表达式\(
,\)
其中每个子表达式都使用 OR 组合在一起-o
(默认情况下,find 的谓词是 AND 组合)。即它读作“max深度 1 AND 类型目录 AND (dir1 OR dir2 OR dir3)”。
请注意,如果没有括号来强制优先,它将被解释为“max深度 1 AND 类型目录 AND dir1 OR (dir2 OR dir3)”,这不会返回所有现有目录的完整列表。大多数情况下,它要么不返回任何内容,要么只返回 dir3,具体取决于 dir1 是否存在。
如果您希望它查找当前目录下任意位置的子目录,请删除该-maxdepth 1
参数。
如果您希望目录匹配不区分大小写,请使用-iname
而不是-name
.请注意, -name
or的参数-iname
可以是模式而不是固定字符串 - 如果所需的目录名称非常相似,这很有用。例如
find . -maxdepth 1 -type d -iname 'dir[123]' -exec tar cfz archive.tgz {} +
该-exec ....
工作方式很像管道 to xargs
,但内置于 to find
。事实上,xargs
如果您愿意,您可以通过将所有内容替换为-exec
来使用-print0 | xargs -0 -r tar cfz archive.tgz
。例如
find . -maxdepth 1 -type d -iname 'dir[123]' -print0 | \
xargs -0 -r tar cfz archive.tgz
(这使用 NUL 字符作为输出分隔符,因此与包含空格等的目录名一起使用与使用 一样安全-exec
。该-r
选项告诉 xargs 如果没有输入,则不执行任何操作)
答案2
和zsh
:
dirs_to_archive=(some/dir /some/other/dir and/more/dirs)
existing_dirs=($^dirs_to_archive(/N))
if (($#existing_dirs)); then
tar -cf - -- $existing_dirs | xz > file.tar.xz
else
echo >&2 Error: none of the dirs were found
fi
POSIX 等效命令(但请注意,既不是POSIX 命令tar
,也不xz
是 POSIX 命令)类似于:
# The list of dirs in "$@" (the only array in POSIX sh language)
set -- some/dir /some/other/dir and/more/dirs
for dir do
# remove from the array the elements that are not directories like with
# zsh's / glob qualifier above
[ -d "$dir" ] && [ ! -L "$dir" ] && set -- "$@" "$dir"
shift
done
if [ "$#" -gt 0 ]; then
tar -cf - -- "$@" | xz > file.tar.xz
else
echo >&2 Error: none of the dirs were found
fi
答案3
如果您可以使用 bash,则使用扩展通配符 ( extglob
):
$ shopt -s extglob; set -x
$ tar -czf archive.tgz *(here_is_a_dir|here_is_another_one|yet_another_dir)
+ tar -czf archive.tgz
tar: no files or directories specified
如果其中一个或多个存在:
$ touch here_is_a_dir yet_another_dir
+ touch here_is_a_dir yet_another_dir
$ tar -czvf archive.tgz *(here_is_a_dir|here_is_another_one|yet_another_dir)
+ tar -czvf archive.tgz here_is_a_dir yet_another_dir
a here_is_a_dir
a yet_another_dir
(我已经使用了set -x
这样你就可以看到 glob 扩展的结果。)
答案4
应该只与 POSIX sh ( tar.sh
) 一起使用的一种:
#!/bin/sh
first=1
for dir in "$@"; do
if [ "$first" ]; then # this is a bit ugly
set --
first=
fi
if [ -d "$dir" ]; then
set -- "$@" "$dir"
fi
done
echo tar -czf archive.tar.gz "$@" # remove that 'echo'
测试:
$ mkdir here_is_a_dir yet_another_dir
$ ./tar.sh here_is_a_dir here_is_another_one yet_another_dir
tar -czf archive.tar.gz here_is_a_dir yet_another_dir