2

我在 bash 脚本方面没有那么丰富的经验,所以考虑在实践中学习它。最近我试图制作一个简单的脚本,它应该显示至少 1 GB 大小的所有文件,并且面临在名称中转义空格的问题。如果我这样做,它在终端中工作正常:

$ find /home/dem -size +1000M -print|sed -e 's/ /\\ /'
/home/dem/WEB/CMS/WP/Themes/Premium_elegant_themes/ETPSD.rar
/home/dem/VirtualBox\ VMs/Lubuntu13.04x86/Lubuntu13.04x86.vdi
/home/dem/VirtualBox\ VMs/Win7/Win7-test.vdi
/home/dem/VirtualBox\ VMs/FreeBSD9.1/FreeBSD9.1.vdi
/home/dem/VirtualBox\ VMs/backup_Lubuntu13.04x86/Lubuntu13.04x86.vdi
/home/dem/VirtualBox\ VMs/Beini-1.2.3/Beini-1.2.3.vdi
/home/dem/VirtualBox\ VMs/BackTrack5RC3/BackTrack5RC3.vdi
/home/dem/VirtualBox\ VMs/WinXPx32/WinXPx32.vdi

但在这个脚本中:

#!/bin/bash

for i in "$( find /home/dem -size +1000M -print|sed -e 's/ /\\ /' )"
 do 
  res="$( ls -lh $i )"
  echo $res
done 

它给出了错误,并且您可能会看到左侧部分被剥离:

ls: cannot access /home/dem/VirtualBox\: No such file or directory
ls: cannot access VMs/Lubuntu13.04x86/Lubuntu13.04x86.vdi: No such file or directory
ls: cannot access /home/dem/VirtualBox\: No such file or directory
ls: cannot access VMs/Win7/Win7-test.vdi: No such file or directory
ls: cannot access /home/dem/VirtualBox\: No such file or directory
ls: cannot access VMs/FreeBSD9.1/FreeBSD9.1.vdi: No such file or directory
ls: cannot access /home/dem/VirtualBox\: No such file or directory
ls: cannot access VMs/backup_Lubuntu13.04x86/Lubuntu13.04x86.vdi: No such file or directory
ls: cannot access /home/dem/VirtualBox\: No such file or directory
ls: cannot access VMs/Beini-1.2.3/Beini-1.2.3.vdi: No such file or directory
ls: cannot access /home/dem/VirtualBox\: No such file or directory
ls: cannot access VMs/BackTrack5RC3/BackTrack5RC3.vdi: No such file or directory
ls: cannot access /home/dem/VirtualBox\: No such file or directory
ls: cannot access VMs/WinXPx32/WinXPx32.vdi: No such file or directory
-rw-rw-r-- 1 dem dem 3.1G Jul 13 02:54 /home/dem/Downloads/BT5R3-GNOME-32/BT5R3-GNOME-32.iso -rw------- 1 dem dem 1.1G Dec 27 2012 /home/dem/WEB/CMS/WP/Themes/Premium_elegant_themes/ETPSD.rar

我需要脚本来显示带有空格的文件+检索 ls -lh 执行的每个文件的实际大小。没有 sed 格式:

$ find /home/dem -size +1000M -print
/home/dem/WEB/CMS/WP/Themes/Premium_elegant_themes/ETPSD.rar
/home/dem/VirtualBox VMs/Lubuntu13.04x86/Lubuntu13.04x86.vdi
/home/dem/VirtualBox VMs/Win7/Win7-test.vdi
/home/dem/VirtualBox VMs/FreeBSD9.1/FreeBSD9.1.vdi
/home/dem/VirtualBox VMs/backup_Lubuntu13.04x86/Lubuntu13.04x86.vdi
/home/dem/VirtualBox VMs/Beini-1.2.3/Beini-1.2.3.vdi
/home/dem/VirtualBox VMs/BackTrack5RC3/BackTrack5RC3.vdi
/home/dem/VirtualBox VMs/WinXPx32/WinXPx32.vdi
4

3 回答 3

3

xargs非常适合简单的情况,尽管-0在处理路径中带有换行符的文件名时(在 UNIX 上是合法的),它需要(以 NUL 分隔的输入)才能正确运行。如果您确实需要将文件名读入 shell 脚本,您可以这样做:

while IFS='' read -r -d '' filename; do
  ls -lh "$filename"
done < <(find /home/dem -size +1000M -print0)

...或者像这样,使用现代版本的 POSIX 标准中的功能find来复制 xargs 的行为:

find /home/dem -size +1000M -exec ls -lh '{}' +
于 2013-08-10T22:03:54.667 回答
2

只需使用xargs

find /home/dem -size +1000M -print0 | xargs -0 ls -lh
于 2013-08-10T21:50:13.107 回答
2

在 shell 脚本中,参数由空格分隔,如果您要查找包含空格的文件名,可能会很麻烦。当您使用for循环时,这是一个问题,因为for循环会将每个空格视为参数分隔符:

$ ls -l
this is file number one
this is file number two

$ for file in $(find . -type f)
> do
>     echo "My file is '$file'"
> done
my file is 'this'
my file is 'is'
my file is 'file'
my file is 'number'
my file is 'one'
my file is 'this'
my file is 'is'
my file is 'file'
my file is 'number'
my file is 'two'

在这种情况下,for将每个空间视为一个单独的文件,这是您不想要的。还有其他问题for

  • for循环在完成处理$(...).
  • 可能会超出您的命令行缓冲区。shell 所做的是执行命令$(...)并用该命令的结果替换$(...)。如果您使用的find命令返回了几十万个文件,您可能会超出命令行缓冲区。更糟糕的是,它会悄无声息地发生。除非你看一下,否则你永远不会知道文件被删除了。事实上,我看到有人使用这种for ... $(...)循环测试 shell 脚本认为一切都很好,但是在非常危急的情况下命令失败了。
  • 它效率低下,因为它必须生成一个单独的 shell 进程。好吧,这已经不是什么大问题了,但仍然......

处理此问题的更好方法是使用while read循环。在 BASH 中,它看起来像这样:

find ... -print0 | while read -d $'\0' file
do
   ....
done

-print0参数打印出所有找到的文件,但用一个NULL字符分隔它们。该while read -d\$0 ...语法打破了NULL字符上的参数名称,而不是像通常那样在新行上。因此,即使您的文件中有新行(并且 Unix 中允许文件名包含新行,while read -d\$0...仍然会正确读取您的文件名。

更好的是,这解决了其他一些问题:

  • 命令行缓冲区不能过载。
  • 您的while read循环将与find. 无需find先找到所有文件。
  • 你没有产生一个单独的进程。

观察:

$ ls -l
this is file number one
this is file number two

$ find . -type f -print0 | while read -d\$0 file
>     echo "My file is '$file'"
> done
my file is 'this is file number one'
my file is 'this is file number two'

顺便说一句,另一个调用的命令xargs有一个类似的参数:

find . -type f -mtime +100 -print0 | xargs -0 rm

xargs命令从 STDIN 获取文件名,并将它们传递给给定的命令。它保证传递的参数不会超出命令行缓冲区。如果他们这样做,xargs将多次运行传递给它的命令。

通常, (like for)xargs在空格上解析文件名。但是,您可以向它传递一个参数来解析空值上的名称。

此参数因系统而异

很抱歉大喊大叫,但我需要说得很清楚。不同的系统对xargs命令有不同的参数,您需要参考手册页来查看您的系统采用的参数。在我的 Mac 上,它是-0. 在 GNU 上,--null虽然一些 Linux 发行版也采用-0了这种方式。而且,一些 Unix 版本甚至可能没有这个参数。

于 2013-08-11T03:49:20.173 回答