代码之家  ›  专栏  ›  技术社区  ›  Richard T

文件名和目录名是否有“转义转换器”?

  •  8
  • Richard T  · 技术社区  · 15 年前

    有一天,我不得不编写一个bash脚本,它遍历任意目录树,查看任意文件,并试图确定它们之间的比较。我想只需要几个小时 最上等的! 过程-不是这样!

    我的宿醉是有时会有个白痴-啊哈!-对不起, 可爱的用户 选择在目录和文件名中放置空格。这会导致我的脚本失败。

    完美的解决方案 除了威胁那些坚持在这样的地方使用空间的人使用断头台(更别提那些把这个放在操作系统代码中的人了!),可能是一个“转义”文件名和目录名的例程,类似于cygwin如何将例程从UNIX转换为DOS文件名格式。在标准的Unix/Linux发行版中有类似的东西吗?

    注意,简单的 for file in * 当一个人试图比较目录树时,构造的工作就不那么好了 只有 在“当前目录”上工作——在这种情况下,和许多其他目录一样,不断地CDing到不同的目录位置会带来自身的问题。所以,在做作业时,我发现了这个问题 Handle special characters in bash for...in loop 这里提出的解决方案挂起了目录名中的空格,但可以简单地这样克服:

    dir="dirname with spaces"
    ls -1 "$dir" | while read x; do
       echo $x
    done
    

    请注意: 上面的代码并不特别出色,因为while循环内部使用的变量在while循环外部是不可访问的。这是因为当ls命令的输出通过管道传输时,会创建一个隐含的子shell。 这是我提问的一个关键激励因素!

    …好吧,上面的代码在很多情况下都有帮助,但是“转义”字符也非常强大。例如,上面的dir可能包含:

    dir\ with\ spaces
    

    这是否已经存在,我只是忽略了它?

    如果没有,是否有人有一个简单的建议写一个-可能与塞德或莱克斯?(我也远不能胜任。)

    6 回复  |  直到 8 年前
        1
  •  4
  •   Dennis Williamson    15 年前

    为测试创建一个非常讨厌的文件名:

    mkdir escapetest
    cd escapetest && touch "m'i;x&e\"d u(p\nmulti)\nlines'\nand\015ca&rr\015re;t"
    

    [编辑: 很可能我是故意的 touch 命令:

    touch $'m\'i;x&e\"d u(p\nmulti)\nlines\'\nand\015ca&rr\015re;t'
    

    这会在文件名中添加更多丑陋的字符。输出看起来有点不同。 ]

    然后运行这个:

    find -print0 | while read -d '' -r line; do echo -en "--[${line}]--\t\t"; echo "$line"|sed -e ':t;N;s/\n/\\n/;bt' | sed 's/\([ \o47()"&;\\]\)/\\\1/g;s/\o15/\\r/g'; done
    

    输出应该如下所示:

    --[./m'i;x&e"d u(p
    multi)
    lines'
    re;t]--         ./m\'i\;x\&e\"d\ u\(p\\nmulti\)\\nlines\'\\nand\\015ca\&rr\\015re\;t
    

    这包括的压缩版本 帕斯卡·蒂凡特 sed 怪物,加上处理回车和换行,也许更多。

    第一次通过 塞德 将多行合并为一行,并用“\n”分隔,用于具有换行符的文件名。第二遍将字符列表中的任何字符替换为其前面的反斜杠。最后一部分将回车替换为“\r”。

    要注意的一点是,正如你所知, while 将处理空间和 for 不会,但通过发送 find 以空结尾并设置的分隔符 read 若为空,还可以处理文件名中的换行符。这个 -r 选择原因 阅读 接受反斜杠而不解释。

    编辑:

    另一种避免特殊字符的方法,这次不用 塞德 ,使用bash的报价和变量创建功能 printf 内置(这也说明使用流程替换而不是管道):

    while read -d '' -r file; do echo "$file"; printf -v name "%q" "$file"; echo "$name"; done< <(find -print0)
    

    变量 $name 将在循环外部可用,因为使用进程替换可防止在循环周围创建子shell。

        2
  •  2
  •   Fritz G. Mehner    15 年前

    以下代码段处理所有文件名(包括空格、引号、换行符等):

    startdir="${1:-.}"                              # first parameter or working directory
    
    #-------------------------------------------------------------------------------
    #  IFS is undefined
    #  read:
    #  -r  do not allow backslashes to escape any characters
    #  -d  delimiter is \0  (not a valid character in a filename)
    #  done < <( find ... ) . redirection from a process substitution
    #-------------------------------------------------------------------------------
    while IFS=  read -r -d '' file; do
      echo "'$file'"
    done < <( find "$startdir" -type f -print0 )
    

    另请参见 BashFAQ .

        3
  •  2
  •   Gordon Davisson    15 年前

    转义方法有一个相当严重的问题:需要什么转义取决于变量将要扩展的上下文,在通常情况下,没有转义可以工作。例如,如果你要做一些简单的事情,比如:

    touch a "b c" d
    files="a b\ c d"
    ls $files
    

    …它不会工作(ls查找4个文件:“a”、“b”、“c”和“d”),因为shell在单词拆分$files时不注意转义。你可以使用 eval ls $files 但在文件名中的制表符之类的东西上会失败。

    这个 while ... read ... done < <(find ... -print0) fgm建议的方法工作可靠(并且由于find的搜索模式的灵活性,非常强大),但对于各种可能的问题,它也是一堆相当混乱的解决方法;如果不需要find的能力,就不难完成任务。 for * :

    shopt -s nullglob    # In case of empty directories...
    for filepath in "$dir"/*; do    # loop over all files in the specified directory
        filename="${filepath##*/}"    # You just wanted the files' names?  No problem.
        echo "$filename"
    done
    

    如果(正如您在问题中提到的)您有兴趣比较两个目录树,那么循环浏览其中一个目录树并不完全符合您的需求;最好将它们的内容放入数组中,如下所示:

    shopt -s nullglob
    pathlist1=("$dir1"/*)    # Get a list of paths of files in dir1
    filelist1=("${pathlist1[@]##*/}")    # Parse off just the filenames
    pathlist2=("$dir2"/*)    # Same for dir2
    filelist2=("${pathlist2[@]##*/}")
    # now compare filelist1 with filelist2...
    

    (注意,Afaik "${pathlist2[@]##*/}" 构造不是标准的,但似乎在bash和zsh中都支持了一段时间。)

        4
  •  2
  •   Community Romance    7 年前

    我发现了这个 How to escape file names in bash shell scripts 在google上我引用了以下内容:

    在和巴什打了很久之后 有一段时间,我发现 下面的代码提供了一个很好的基础 用于转义特殊字符。 当然,它不是完整的,但是 最重要的字符是 过滤的

    如果有人有更好的解决方案, 请告诉我。它起作用了 可读但不美观。

    FILE_ESCAPED=`echo "$FILE" | \
    sed s/\\ /\\\\\\\\\\\\\\ /g | \
    sed s/\\'/\\\\\\\\\\\\\\'/g | \
    sed s/\&/\\\\\\\\\\\\\\&/g | \
    sed s/\;/\\\\\\\\\\\\\\;/g | \
    sed s/\(/\\\\\\\\\\(/g | \
    sed s/\)/\\\\\\\\\\)/g `
    

    也许你可以用它作为起点。

        5
  •  1
  •   Ignacio Vazquez-Abrams    15 年前
    #!/bin/bash
    
    while read filename; do
      echo 'I am doing something with "'"$filename"'".'
    done < <(find)
    

    请注意 <( ) 当bash作为 /bin/sh .

        6
  •  0
  •   ennuikiller    15 年前

    find命令有时在这种情况下有效:

    find . -exec ls {} \;
    

    例如