处理 bash 脚本中的特殊字符

Handling special characters in bash script

我不熟悉 bash 脚本。也许这是一个愚蠢的问题。但我找不到答案。我正在编写一个 bash 脚本,该脚本模仿命令 ls -sh 的行为,但实际上使用 du -sh 来获取文件和文件夹大小。并对输出进行排序。很像 du -sh* | sort -h 的颜色。

#!/usr/bin/bash

if [ "$#" = "0" ]
then
    du -sh *|awk -f /path/to/color-ls.awk|sort -h
else
    du -sh $@|awk -f /path/to/color-ls.awk|sort -h
fi

其中 ls-color.awk 是:

# color-ls.awk
size=;
name=;
for (i=3; i<=NF; i++)
{
    tmp=(name " " $i);
    name=tmp
}
# filename=([=12=] ~ /'/)? ("\"" name "\""):("'" name "'")
filename=("'" name "'")
printf  " "
cmd=("ls -d " filename " --color")
system(cmd)

使用 ls --color 为 du -sh

的输出着色的 awk 脚本

我的脚本适用于大多数文件名,即使是包含空格的文件名。但它有一些涉及特殊字符的问题,我不知道如何解决。

1。当 运行 没有参数时:

它正在解释任何包含导致错误的单引号的文件名

sh: 1: Syntax error: Unterminated quoted string

2。当 运行 带有参数时:

和没有参数一样的问题。它将带空格的文件名解释为两个名称。

示例:当在名为 VirtualBox VMs 的文件夹上使用时,或者当在我的主目录中将 * 作为参数给出时,它的输出是:

du: cannot access 'VirtualBox': No such file or directory
du: cannot access 'VMs': No such file or directory

3。我想要的:

我希望脚本跳过特殊字符并将它们原样传递给 du

4。我尝试了什么:

我尝试在每个文件名前后添加双引号

parse(){
    for arg in $@
    do
        printf "\"$arg\"\n"
    done
}

但似乎没有用。 du 不接受附加到文件名的引号。

du: cannot access '"VirtualBox': No such file or directory
du: cannot access 'VMs"': No such file or directory

此外,用 \' 替换引号也无济于事。也许我只是做错了。

# du -sh $(printf "file'name\n" |sed "s/'/\\'/g")
du: cannot access 'file\'\''name': No such file or directory
# ls file\'name 
"file'name"

空格也是如此

du: cannot access 'VirtualBox\': No such file or directory
du: cannot access 'VMs': No such file or directory

5。额外:

我正在尝试使脚本正常工作 ls -sh 会工作,但输出已排序并且在涉及文件夹时具有更准确的结果。但是当向它提供参数时,此脚本的工作方式类似于 ls -sh -dlh Desktop 显示桌面的大小,而不是桌面内单个文件和文件夹的大小。我相信这可以通过一个循环来解决,该循环检查每个参数是文件还是文件夹并相应地执行 du -sh 然后排序。

#!/usr/bin/bash

if [ "$#" = "0" ]
then
    du -sh *|awk -f /path/to/color-ls.awk|sort -h
else
    for i in $@
    do
        if [[ -d "$i" ]]; then
            du -sh $i/* |awk -f /path/to/color-ls.awk
        else
            du -sh "$i" |awk -f /path/to/color-ls.awk
        fi
    done|sort -h
fi

我希望找到最佳方法。

提前致谢。

由于您没有包含 shopt -s nullglob,很可能 Desktop/* 没有扩展到任何奇怪的文件,除非那里真的没有文件,您启用了 nullglob 在交互模式下,du -sh 实际上不显示 Desktop.

中文件的大小

您也可能从 Desktop/ 不存在的地方调用脚本。

您可以添加打印 $PWD 的调试语句。您也可以使用 bash -x.

尝试 运行 脚本

在你的脚本中,我建议启用 nullglob 然后修改它,这样如果目标目录不包含文件,就不会调用 du -sh

类似于:

set -- "$i"/*; [[ $# -gt 0 ]] && du -sh -- "$@" ...

另外$@展开时要加引号

for i in "$@"; do

这可以简化为for i; do,但我们将修改循环内的位置参数,因此我们展开"$@"

您也可以选择将展开的文件存储在数组中。

一个问题请不要post这么多。请每题一道题。每个问题一个脚本等

确保使用 shellcheck 检查您的脚本。它会抓住你的错误。参见 https://mywiki.wooledge.org/Quotes

  1. When run without arguments:
awk 脚本中的

filename=("'" name "'") 是将带有 ' 引号的任何内容传递给 system() 调用的无效方式,因此您会收到未终止的 ' 错误,正如预期的那样,因为会有 3 ' 个字符。修复 AWS 脚本,或者更好地在 Bash 中重写它,不需要 awk。也许用 Python 或 Perl 重写它。

此外,tmp=(name " " $i); 从文件名中删除制表符和多个空格。这一切都是为了只使用漂亮的文件名。

脚本无论如何都会在文件名中的换行符处中断。

  1. When run with arguments:

$@ 进行了 分词 文件名扩展 (您应该研究的主题)。单词拆分将输入拆分为空格上的单词。使用 "$@"。引用扩展。

  1. What I want:

您将使用 "$@"

  1. What I tried:

变量内容无关。您必须更改 使用 变量的方式,而不是它的内容。 IE。在变量的 use 周围使用引号。不是内容。

  1. Extra:

你没有引用扩展。使用 "$i" 而不是 $i。是 "$i"/*</code> 进行分词。</p> <hr /> <p>最后,在所有这些之后,使用 GNU 工具,您的脚本可能看起来像:</p> <pre><code>if (($# == 0)); then set -- * fi du -hs0 "$@" | sort -zh | sed -z 's/\t/\x00/' | while IFS= read -r -d '' size && IFS= read -r -d '' file; do printf "%s " "$size"; ls -d "$file" done

另见 How can I find and safely handle file names containing newlines, spaces or both? https://mywiki.wooledge.org/BashFAQ/001

此外,您可以链接任何语句:

if stuff; then
   stuff1
else
   stuff2
fi | 
sort -h |
awk -f yourscriptrt 

也不要重复自己 - 使用 bash 数组:

args=()
if stuff; then
  args=(*)
else
  args=("$@")
fi
du -hs "${args[@]}" | stuff...

这样 sort 就可以减少工作量,我会把它放在 du 之后,而不是解析之后。