仅当目录存在时如何压缩目录列表

仅当目录存在时如何压缩目录列表

我有一个要存档的目录列表。但有时它们并不全部存在。

我希望能够通过提供目录列表来创建存档,并且它只会存档现有的目录,并忽略丢失的目录。 (但如果不存在目录,仍然会失败)

这对我很有用,因为我正在运行一些持续集成,并且某些流程创建了某些我想在存档中保留以供将来使用的工件,我知道可以创建的所有可能路径,但并不总是确定哪些路径被创建。

假设可能的路径是: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.请注意, -nameor的参数-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

相关内容