【问题标题】:how to handle spaces in shell scripts如何处理 shell 脚本中的空格
【发布时间】:2014-09-14 21:44:05
【问题描述】:

我正在尝试编写一个bash脚本来列出当前目录的每个文件/子目录的大小,如下:

for f in $(ls -A)
do
    du -sh $f
done

我使用了ls -A,因为我需要包含以点开头的隐藏文件/目录,例如.ssh。但是,如果$f 中的文件名包含空格,则上述脚本无法处理空格。 例如我有一个名为:

books to borrow.doc

上面的脚本会返回:

du: cannot access `books': No such file or directory
du: cannot access `to': No such file or directory
du: cannot access `borrow.doc': No such file or directory

有一个类似的问题Shell script issue with filenames containing spaces,但要处理的名称列表来自扩展*(而不是ls -A)。该问题的答案是将双引号添加到$f。我尝试了相同的方法,即更改

    du -sh $f

    du -sh "$f"

但结果是一样的。我的问题是如何编写脚本来处理这里的空格?

谢谢。

【问题讨论】:

  • 你可以改用find ... -execdir ...
  • 问题是空格是内部字段分隔符(IFS)。使用find -print0 … 或临时覆盖IFS 变量。
  • 类似shopt -s nullglob dotglob; for f in *; do du -sh "$f"; done ?
  • @lxg,修改 IFS 并不能防止通配,因此仅保证不带引号的扩展安全是不够的(如果设置了 nullglob,它可能会在人们无法预料的情况下产生影响)。

标签: bash shell whitespace


【解决方案1】:

不要解析来自ls 的输出。当文件包含空格时,$f 包含在空格上拆分的文件名部分,因此双引号不会得到整个文件名

下一个将起作用,并且与您的脚本相同

GLOBIGNORE=".:.."  #ignore . and ..
shopt -s dotglob   #the * will expand all files, e.g. which starting with . too
for f in *
do
    #echo "==$f=="
    du -sh "$f"  #double quoted (!!!)
done

【讨论】:

  • 不应该直接将GLOBIGNORE设置为.:..,因为The file names . and .. are always ignored when GLOBIGNORE is set and not null.
  • 是的,但不要伤害,并确保它确实设置为某些东西。
  • 是的。在写我之前的评论时,我实际上对它是如何工作的有点困惑。 ... 仍然不会匹配一个裸的 *,即使 GLOBIGNORE 被清空并且 dotglob 被设置(大概是因为 ... 不是真正的文件名)。出于某种原因,它们将匹配 .*,除非您将 GLOBIGNORE 设置为非空值。
【解决方案2】:

除非目录太大以至于文件名列表太大:

du -sh * .*

请注意,这将包括 ...。如果你想消除..(可能是个好主意),你可以使用:

for file in * .*
do
    [ "$file" = ".." ] && continue
    du -sh "$file"  # Double quotes important
done

您可以考虑将名称分配给一个数组,然后处理该数组:

files=( * .* )
for file in "${files[@]}"
do
    ...
done

您可以使用它的变体在名称组上运行 du,但您也可以考虑使用:

printf "%s\0" "${files[@]}" | xargs -0 du -sh

【讨论】:

  • ( shopt -s dotglob; du -sh * ) 也会消除 ...。为什么.* 匹配... 仍然有点困惑
  • 我尝试了第二个选项。 files=( * .*) ...,我得到的只是1.7G . 1.7G . 1.7G . ... 我错过了什么吗?
  • 这取决于你用什么代替三个点。如果是du -sh "$file",应该没问题。如果你放别的东西,肯定有办法让你一遍又一遍地得到相同的大小(du -sh "$files",复数名称就是这样一种)。
  • 我使用的是du -sh $file。现在我意识到我忘了添加引号,我已经完全尝试了你的选项 1,我得到了一堆 du: invalid zero-length file name。我正在使用来自 mingw.org 的 MinGW32。可能是什么问题?
【解决方案3】:

如果for 循环会引起头痛,我通常更喜欢使用程序find。就你而言,这真的很简单:

$ find . -maxdepth 1 -exec du -sh '{}' \;

使用-exec 存在许多安全问题,这就是为什么GNU find 支持更安全的-execdir,如果可用的话应该首选。由于我们在这里没有递归到目录中,因此并没有真正的区别。

find 的 GNU 版本还有一个选项 (-print0) 可以打印出由 NUL 字节分隔的匹配文件名,但我发现上述解决方案比首先输出所有文件的列表更简单(也更有效)名称,然后将其拆分为 NUL 字节,然后对其进行迭代。

【讨论】:

  • -execdir 本身就是一个非传统选项——所以你已经不支持纯 POSIX 查找了。
  • @CharlesDuffy 谢谢,我忘了这个;更新了我的答案。
【解决方案4】:

试试这个:

    ls -A |
    while read -r line
    do
    du -sh "$line"
    done

while 循环不是逐字检查ls -A 输出,而是逐行检查。 这样,您无需更改 IFS 变量。

【讨论】:

    【解决方案5】:

    是时候总结一下了。假设您使用的是 Linux,这应该适用于大多数(如果不是全部)情况。

    find -maxdepth 1 -mindepth 1 -print0 | xargs -r -0 du -sh
    

    【讨论】:

      猜你喜欢
      • 2013-12-25
      • 2017-09-27
      • 2011-09-01
      • 2013-06-10
      • 2010-11-07
      • 2011-03-25
      • 1970-01-01
      • 2011-10-21
      • 2014-05-16
      相关资源
      最近更新 更多