主要问题:
我正在编写一个脚本来执行备份。我通过以下方式创建文件列表:
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 假设Python
和Tutorial.pdf
是不同的文件。
有没有办法制作包含反斜杠的文件列表find
,或者我应该更改为ls
与管道一起使用?
我想阅读您的提示和建议。
我的另一个问题:
这个问题的答案并不重要,但一个提示可能会有所帮助
如何列出仅包含文件名而不包含文件路径的文件?
例如:file
而不是/home/user/file
.
我尝试使用find
命令prune
和path
选项,但没有成功。
答案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
如果您不需要路径前缀
有多种使用 和 的方法tar
,pax
但最简单的方法是首先更改为要存档的目录。
( 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
希望这可以帮助!