使用unicode文件名的可移植(跨平台)脚本

jm6*_*666 10 bash

这让我发疯了.有下一个bash脚本.

testdir="./test.$$"
echo "Creating a testing directory: $testdir"
mkdir "$testdir"
cd "$testdir" || exit 1

echo "Creating a file word.txt with content á.txt"
echo 'á.txt' > word.txt

fname=$(cat word.txt)
echo "The word.txt contains:$fname"

echo "creating a file $fname with a touch"
touch $fname
ls -l

echo "command: bash cycle"
while read -r line
do
    [[ -e "$line" ]] && echo "$line is a file"
done < word.txt

echo "command: find . -name $fname -print"
find . -name $fname -print

echo "command: find . -type f -print | grep $fname"
find . -type f -print | grep "$fname"

echo "command: find . -type f -print | fgrep -f word.txt"
find . -type f -print | fgrep -f word.txt
Run Code Online (Sandbox Code Playgroud)

在Freebsd上(也可能在Linux上)给出了结果:

Creating a testing directory: ./test.64511
Creating a file word.txt with content á.txt
The word.txt contains:á.txt
creating a file á.txt with a touch
total 1
-rw-r--r--  1 clt  clt  7  3 júl 12:51 word.txt
-rw-r--r--  1 clt  clt  0  3 júl 12:51 á.txt
command: bash cycle
á.txt is a file
command: find . -name á.txt -print
./á.txt
command: find . -type f -print | grep á.txt
./á.txt
command: find . -type f -print | fgrep -f word.txt
./á.txt
Run Code Online (Sandbox Code Playgroud)

即使在Windows 7(安装了cygwin)中运行脚本也能得到正确的结果.

但是当我在OS X bash 上运行这个脚本时,得到了这个:

Creating a testing directory: ./test.32534
Creating a file word.txt with content á.txt
The word.txt contains:á.txt
creating a file á.txt with a touch
total 8
-rw-r--r--  1 clt  staff  0  3 júl 13:01 á.txt
-rw-r--r--  1 clt  staff  7  3 júl 13:01 word.txt
command: bash cycle
á.txt is a file
command: find . -name á.txt -print
command: find . -type f -print | grep á.txt
command: find . -type f -print | fgrep -f word.txt
Run Code Online (Sandbox Code Playgroud)

所以,只有bash找到文件á.txt没有,find也没有grep.:(

首先询问apple.stackexchange和一个建议使用iconv转换文件名的答案.

$ find . -name $(iconv -f utf-8 -t utf-8-mac <<< á.txt)
Run Code Online (Sandbox Code Playgroud)

虽然这适用于"OS X",但无论如何它都很糟糕.(需要为每个输入到终端的utf8字符串输入另一个命令.)

我正试图找到一个通用的跨平台bash编程解决方案.所以,问题是:

  • 为什么在OS X上bash"找到"文件而find不是?

  • 如何编写跨平台bash脚本,其中unicode文件名存储在文件中.
  • 唯一的解决方法是编写特殊版本仅适用于Mac OS X的iconv
  • 是否存在其他脚本语言的便携式解决方案perl

Ps:最后,不是真正的编程问题,但想知道Apple使用分解的文件名决定什么是理由不能用命令行很好地解决 utf8

编辑

简单od.

$ ls | od -bc
0000000   141 314 201 056 164 170 164 012 167 157 162 144 056 164 170 164
           a   ?    **   .   t   x   t  \n   w   o   r   d   .   t   x   t
0000020   012                                                            
          \n   
Run Code Online (Sandbox Code Playgroud)

$ od -bc word.txt
0000000   303 241 056 164 170 164 012                                    
           á  **   .   t   x   t  \n                                    
0000007
Run Code Online (Sandbox Code Playgroud)

所以

$ while read -r line; do echo "$line" | od -bc; done < word.txt
0000000   303 241 056 164 170 164 012                                    
           á  **   .   t   x   t  \n                                    
0000007
Run Code Online (Sandbox Code Playgroud)

并且从一个发现的出口是相同的 ls

$ find . -print | od -bc
0000000   056 012 056 057 167 157 162 144 056 164 170 164 012 056 057 141
           .  \n   .   /   w   o   r   d   .   t   x   t  \n   .   /   a
0000020   314 201 056 164 170 164 012                                    
           ?    **   .   t   x   t  \n      
Run Code Online (Sandbox Code Playgroud)

因此,word.txtIS 的内容不同于从其内容创建的文件.因此,仍然没有解释为什么bash找到该文件.

n. *_* m. 5

Unicode 很难。每次刷牙时都要重复一遍。

您的á.txt文件名包含 5 个字符,其中á是麻烦的一个。有不止一种方法可以表示á为 Unicode 代码点序列。有预先组合的表示和分解的表示。不幸的是,大多数软件都没有准备好处理字符,而是解决了代码点(是的,大多数软件都是 cr*p)。这意味着给定相同字符的预组合和分解表示,软件不会将它们识别为相同。

您有一个预组合的á,表示为 Unicode 代码点 U+00E1 带有 ACUTE 的拉丁文小写字母 A。Windows 使用预组合表示。Mac 文件系统坚持分解表示(嗯,主要是;utf-8-mac 不分解某些字符范围,但á分解正常)。因此,在 Mac 上,您á会变成 U+0061 拉丁小写字母 A,然后是 U+0301 组合 ACUTE ACCENT(记下我的头顶,手头没有 Mac)。Linux 文件系统接受你扔给它们的任何东西。

如果你给find一个 precomposed á,它不会找到一个á名字中带有分解的文件,因为它不准备处理这个骚动。

那么有什么解决办法呢?没有。如果要处理 Unicode,则必须解决常用工具的缺陷。

这是一种稍微不那么丑陋的解决方法。编写一个小bash函数(使用iconv或其他),为每个系统转换该系统可接受的表示,并在整个过程中使用它。让我们称之为u8

find . -name $(u8 $myfilename) -print 
find . -name -type f -print | fgrep $(u8 $myfilename)
Run Code Online (Sandbox Code Playgroud)

等等。漂亮不是,但它应该工作。

哦,我认为我们都应该开始为此 cr*p 发送错误报告。我们的软件最终应该努力理解基本的人类概念,比如字符(我什至还没有开始谈论字符串)。代码点只是不会削减它,抱歉,即使它们是 Unicode 代码点。