如何在需要时将包含反斜杠的文件列表存储在变量中?

如何在需要时将包含反斜杠的文件列表存储在变量中?

主要问题:

我正在编写一个脚本来执行备份。我通过以下方式创建文件列表:

LISTOFFILES=$(find ~ \( -name '*.[pP][dD][fF]' -o -name '*.[oO][dD][tT]' \))

该变量LISTOFFILES或多或少存储如下内容:

/home/user/file1.pdf /home/user/file2.odt /home/user/Python Tutorial.pdf

我的下一步是:

tar cvf backup.tar $LISTOFFILES

bash 响应是:

/home/user/file1.pdf
/home/user/file2.odt
/home/user/Python: Cannot stat: No such file or directory
tar: Tutorial.pdf

我知道 Bash 假设PythonTutorial.pdf是不同的文件。

有没有办法制作包含反斜杠的文件列表find,或者我应该更改为ls与管道一起使用?

我想阅读您的提示和建议。

我的另一个问题:

这个问题的答案并不重要,但一个提示可能会有所帮助

如何列出仅包含文件名而不包含文件路径的文件?

例如:file而不是/home/user/file.

我尝试使用find命令prunepath选项,但没有成功。

答案1

至于您的主要问题,您需要采取不同的方法。您的方法是将所有文件放入 $LISTOFFILES 中的单个字符串中。然后,当您访问它时,您不会使用引号,这会导致它在空格上分开并给出您所看到的结果。

获得所需结果的最简单方法如下:

find ~ \( -name '*.[pP][dD][fF]' -o -name '*.[oO][dD][tT]' \) -print0 | xargs -0 tar cvf backup.tar

我们所做的就是运行 find,并用 NULL 字符(-print0 部分)分隔每个结果。 find 的输出然后通过管道传输到 xargs,xargs 读取由 NULL 字符分隔的文件列表(-0 参数告诉 xargs 执行此操作),并将该文件列表作为参数传递给“tar”。

对于第二个问题:如果您只想从find命令中获取文件名,请使用 printf

find /path/to -printf '%f\n'
file.txt

如果您的意思是一般情况,而不仅仅是 find,那么使用basename.

# basename /path/to/file.txt
file.txt

basename 的设计正是为了做到这一点,除此之外别无其他。

答案2

为什么您的尝试失败以及如何解决它

您可以累积返回的文件名列表find,但前提是文件名中没有换行符,并且您需要采取预防措施。

当您编写不带引号的命令替换或变量扩展时(如下$LISTOFFILES所示),shell 会执行分词通配符(通配符扩展)关于结果。

规则:始终在命令替换和变量扩展两边加上双引号。
例外:如果您了解为什么需要省略双引号,以及为什么这样做是安全的。

在这里,您需要去掉双引号,因为分词是将字符串分割成换行符分隔的位。但默认情况下它也会在其他空白字符处进行分割。因此,您需要确保安全,并关闭通配符。您可以通过设置来完成前者IFS多变的到单个换行符和后者通过调用set -f

IFS='
'
set -f
tar cvf backup.tar $LISTOFFILES

请注意,不涉及反斜杠。当您直接输入文件名时使用反斜杠,而不是当它们由find.

常用的使用方法find

您不应该解析 的输出find,而应该让它通过操作调用您想要在文件上运行的命令-exec。这可以解决文件过多以致超过最大命令行长度的情况:根据需要多次调用程序。

在这里,这有点困难,因为您不能tar -c多次调用(每次调用时都会从新的空存档开始)。但看Tar 压缩目录中的所有 PDF,保留目录结构

更简单的方法

如果文件不多,并且您正在运行 ksh93 或 bash ≥4 或 zsh,则使用**通配符递归到子目录。您需要先设置一些 shell 选项:

set -o globstar                  # on ksh93
shopt -s globstar -s extglob     # on bash 4
setopt ksh_glob                  # on zsh
tar cvf backup.tar ~/**/*.@(PDF|pdf|ODT|odt)

在 zsh 中,无需特殊设置,您可以将该模式编写为~/**/*.(PDF|pdf|ODT|odt).之后setopt extglob,您可以将其写为~/**/(#i)*.(pdf|odt).

或者你可以使用帕克斯:

pax -w -x ustar -s '/\.pdf$/&/' -s '/\.PDF$/&/' -s '/\.odt$/&/' -s '/\.ODT$/&/' -s '/.*//' ~ >backup.tar

如果您不需要路径前缀

有多种使用 和 的方法tarpax但最简单的方法是首先更改为要存档的目录。

( IFS='
'; set -f; cd ~ && tar cvf /path/to/backup.tar $(find . …) )
( cd ~ && pax -w … . ) >backup.tar

答案3

第一个问题:

你的第一个问题的答案是quotes

而不是做->tar cvf backup.tar $LISTOFFILES

这样做->tar cvf backup.tar "$LISTOFFILES"

让我们看一个示例(我为这个演示获取了您的一个文件,即使该文件在我的计算机上不存在,请查看我收到的带引号和不带引号的错误)-

[jaypal:~] file="/home/user/Python Tutorial.pdf"

[jaypal:~] ls $file
ls: /home/user/Python: No such file or directory
ls: Tutorial.pdf: No such file or directory

两个文件/home/user/Python和出现错误Tutorial.pdf。让quote我们来variable看看我们会得到什么。

[jaypal:~] ls "$file"
ls: /home/user/Python Tutorial.pdf: No such file or directory

文件仅出现一次错误/home/user/Python Tutorial.pdf

第二个问题:

修剪/path/to/file有几种方法。我最喜欢的是,awk但我也会给你看一个sed

sed方法:

[jaypal:~/Temp] echo "$filename"
/home/user/file1.pdf /home/user/file2.odt /home/user/Python Tutorial.pdf

[jaypal:~/Temp] echo "$filename" | sed 's@/home/user/@@g'
file1.pdf file2.odt Python Tutorial.pdf

您可以将其包含在您的脚本中,如下所示 -

LISTOFFILES=$(find ~ \( -name '*.[pP][dD][fF]' -o -name '*.[oO][dD][tT]' \) | sed 's@/home/user/@@g' )

这里要记住的是我们正在做的事情substitution。我们正在替换/home/user/nothing.如果您正在find处理来自多个目录的文件,那么您可以进行大量的替换(哎呀!)或者这样做awk

awk方法:

awk -F"/" '{print $NF}'

是的,就是这样。适用于find任何目录或其子目录下的文件。

所以你的脚本可以通过以下方式实现 -

LISTOFFILES=$(find ~ \( -name '*.[pP][dD][fF]' -o -name '*.[oO][dD][tT]' \) | awk -F"/" '{print $NF}')

其他提示和技巧:

以下是一些我认为可能对您有用的随机提示 -

对于find文件名insensitively,请使用iname您现在拥有的名称。看一下这个 -

[jaypal:~/Temp] find . -name "j*.txt"
./jP.txt

[jaypal:~/Temp] find . -iname "j*.txt"
./jj.TXT
./jP.txt

其他修剪方法/path/to/file是使用basename.我将给出一个示例,您可以根据需要将其合并到您的脚本中。

[jaypal:~/Temp] filename="/usr/bin/java.pdf"

[jaypal:~/Temp] echo "$filename"
/usr/bin/java.pdf

[jaypal:~/Temp] basename "$filename"
java.pdf

希望这可以帮助!

相关内容