Ric*_*d T 8 bash filenames escaping
当我必须编写一个BASH脚本来处理任意目录树并查看任意文件并尝试确定它们之间的比较时,这一天到来了.我认为这将是一个简单的几个小时的上衣!过程 - 不是这样!
我的挂断是有时候一些白痴 - 唉! - 对不起,可爱的用户选择在目录和文件名中放置空格.这会导致我的脚本失败.
完美的解决方案,除了为那些坚持在这些地方使用空间的人威胁断头台(更不用说那些把它放在操作系统代码中的人!),可能是一个"逃避"文件和目录名称的例程我们,有点像cygwin如何将例程从unix转换为dos文件格式.在标准的Unix/Linux发行版中有这样的东西吗?
请注意,for file in *
当一个人试图比较目录树时,简单的构造不能很好地工作,因为它只能在"当前目录"上工作 - 而且,在这种情况下和许多其他目录一样,不断地CD到各个目录位置会带来它自己的问题.所以,在做我的作业时,我发现这个问题处理bash for for循环中的特殊字符,并且建议的解决方案挂起了目录名称中的空格,但可以简单地像这样克服:
dir="dirname with spaces"
ls -1 "$dir" | while read x; do
echo $x
done
Run Code Online (Sandbox Code Playgroud)
请注意:上面的代码并不是特别精彩,因为while循环中使用的变量在while循环之外是不可接受的.这是因为当ls命令的输出被管道传输时,会创建一个隐含的子shell.这是我查询的关键激励因素!
...好吧,上面的代码有助于许多情况,但"逃避"角色也会非常强大.例如,上面的dir可能包含:
dir\ with\ spaces
Run Code Online (Sandbox Code Playgroud)
这已经存在了吗?我一直在忽视它?
如果没有,有没有人有一个简单的建议写一个 - 也许与sed或lex?(我对两者都不太称职.)
为测试创建一个非常讨厌的文件名:
mkdir escapetest
cd escapetest && touch "m'i;x&e\"d u(p\nmulti)\nlines'\nand\015ca&rr\015re;t"
Run Code Online (Sandbox Code Playgroud)
[编辑:我可能希望该touch
命令是:
touch $'m\'i;x&e\"d u(p\nmulti)\nlines\'\nand\015ca&rr\015re;t'
Run Code Online (Sandbox Code Playgroud)
这会在文件名中添加更多难看的字符。输出看起来会有点不同。]
然后运行这个:
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
Run Code Online (Sandbox Code Playgroud)
输出应如下所示:
- [。/混合 多) 线' re;t]-- ./m\'i\;x\&e\"d\ u\(p\\nmulti\)\\nlines\'\\nand\\015ca\&rr\\015re\;t
这包括Pascal Thivent sed
怪物的压缩版本,加上回车和换行符的处理,也许还有更多。
sed
对于具有换行符的文件名,第一次遍历将多行合并为由“\n”分隔的行。第二遍将字符列表中的任何字符替换为前面带有反斜杠的字符。最后一部分用“\r”替换回车符。
需要注意的一件事是,如您所知,while
将处理空格,但for
不会处理空格,但通过发送find
带有 null 终止符的输出并将分隔符设置read
为 null,您还可以处理文件名中的换行符。该-r
选项导致read
接受反斜杠而不解释它们。
编辑:
另一种转义特殊字符的方法,这次不使用sed
,使用 Bash 内置的引用和变量创建功能printf
(这也说明了使用进程替换而不是管道):
while read -d '' -r file; do echo "$file"; printf -v name "%q" "$file"; echo "$name"; done< <(find -print0)
Run Code Online (Sandbox Code Playgroud)
该变量$name
将在循环外部可用,因为使用进程替换可以防止在循环周围创建子 shell。