从linux机器传中文文件名的文件到windows,会导致文件名乱码,这时因为linux编码是utf8,windows是gbk。找了一下好像没有类似linux的iconv,convmv的工具。于是自己手写了一个。已经开发成功并经过自测。取名叫utf8togbk.exe。主要功能是两个,一个是修复单个文件的乱码文件名,另一个是指定目录,递归修复包括此目录名在内和此目录里的所有乱码中文名。
贴代码,使用的vs2022,需要取消sdl检查,和至少支持c++17,因为用到了std::filesystem。
#include <windows.h>
#include <iostream>
using namespace std;
#include <filesystem>
namespace fs = std::filesystem;
string Utf8ToGbk(const char* src_str)
{
int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0);
wchar_t* wszGBK = new wchar_t[len + 1];
memset(wszGBK, 0, len * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len);
len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
char* szGBK = new char[len + 1];
memset(szGBK, 0, len + 1);
WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
string strTemp(szGBK);
delete[] wszGBK;
delete[] szGBK;
return strTemp;
}
//————————————————
//版权声明:本文为CSDN博主「踏莎行hyx」的原创文章,遵循CC 4.0 BY - SA版权协议,转载请附上原文出处链接及本声明。
//原文链接:https ://blog.csdn.net/u012234115/article/details/83186386/
string GbkToUtf8(const char* src_str)
{
int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len + 1];
memset(wstr, 0, len + 1);
MultiByteToWideChar(CP_ACP, 0, src_str, -1, wstr, len);
len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
char* str = new char[len + 1];
memset(str, 0, len + 1);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
string strTemp = str;
if (wstr) delete[] wstr;
if (str) delete[] str;
return strTemp;
}
//————————————————
//版权声明:本文为CSDN博主「踏莎行hyx」的原创文章,遵循CC 4.0 BY - SA版权协议,转载请附上原文出处链接及本声明。
//原文链接:https ://blog.csdn.net/u012234115/article/details/83186386/
wstring s2ws(const std::string& s)
{
size_t i;
string curLocale = setlocale(LC_ALL, NULL);
setlocale(LC_ALL, "chs");
const char* _source = s.c_str();
size_t _dsize = s.size() + 1;
wchar_t* _dest = new wchar_t[_dsize];
wmemset(_dest, 0x0, _dsize);
//mbstowcs_s(&i, _dest, _dsize, _source, _dsize);
mbstowcs(_dest, _source, _dsize);
wstring result = _dest;
delete[] _dest;
setlocale(LC_ALL, curLocale.c_str());
return result;
}
string getName(string path)
{
int pos = path.find_last_of("\\");
if (pos != -1)
{
string ret = path.substr(pos + 1);
return ret;
}
pos = path.find_last_of("/");
string ret = path.substr(pos + 1);
return ret;
}
void xiufuluanma(string dir)
{
for (auto& i : fs::directory_iterator(dir))
{
string s = i.path().string();
fs::path sourceFilePath(s);
string destinationPath = dir + Utf8ToGbk(getName(s).c_str());
try
{
fs::path destinationFilePath(destinationPath);
fs::rename(sourceFilePath, destinationFilePath);
}
catch (const fs::filesystem_error& e)
{
std::cerr << "文件名修复失败:" << e.what() << std::endl;
ExitProcess(0);
}
}
for (auto& i : fs::directory_iterator(dir))
{
string s = i.path().string();
wstring ws = s2ws(s);
int ret = GetFileAttributes(ws.c_str());
if (ret == FILE_ATTRIBUTE_DIRECTORY)
{
if (s.back() != '\\' || s.back() != '/')
{
s += '\\';
}
xiufuluanma(s);
}
}
}
char siweilicheng[];
int main(int argc, char* argv[])
{
if (argc < 2)
{
printf("usage \r\n\
utf8togbk.exe utf8乱码文件名\r\n\
utf8togbk.exe utf8乱码文件所在目录\n\r\
utf8togbk.exe -s utf8乱码字符串\n\r\
utf8togbk.exe --info");
return 0;
}
int ret = strcmp(argv[1], "-s");
if (ret == 0 && argc == 3)
{
string s = Utf8ToGbk(argv[2]);
cout << s << endl;
return 0;
}
ret = strcmp(argv[1], "--info");
if (ret == 0 && argc == 2)
{
cout << siweilicheng << endl;
return 0;
}
if (argc != 2)
{
printf("usage \r\n\
utf8togbk.exe utf8乱码文件名\r\n\
utf8togbk.exe utf8乱码文件所在目录\n\r\
utf8togbk.exe -s utf8乱码字符串\n\r\
utf8togbk.exe --info");
return 0;
}
bool isfile = false;
string file = argv[1];
wstring wfile = s2ws(file);
ret = GetFileAttributes(wfile.c_str());
if (ret == -1)
{
return 0;
}
if (ret == FILE_ATTRIBUTE_DIRECTORY)
{
isfile = false;
}
else
{
isfile = true;
}
try
{
fs::path sourceFilePath(file);
string destinationPath = Utf8ToGbk(file.c_str());
fs::path destinationFilePath(destinationPath);
fs::rename(sourceFilePath, destinationFilePath);
file = destinationPath;
}
catch (const fs::filesystem_error& e)
{
std::cerr << "文件名修复失败:" << e.what() << std::endl;
}
if (!isfile)
{
if (file.back() != '\\' || file.back() != '/')
{
file += '\\';
}
xiufuluanma(file);
}
return 0;
}
char siweilicheng[] = { "从linux传文件到windows,因为编码不一致的问题,导致在windows查看中文文件名乱码。于是写了一个工具,能将utf8乱码中文字符串转为gbk正常的中文字符串。\n\r\
但是仅仅如此还不够好用。实际上往往还需要两个需求,一个是直接修复乱码的文件名,另一个是指定目录,批量修复里面所有的乱码的文件名。\n\r\
文件内容基本不会乱码,因为linux和windows文件内容现在都支持utf8,暂时不管。\n\r\
现在的难点是文件名与普通字符串的区分,对于这个,加一个参数 - s表示修复字符串,不加表示修复文件名。\n\r\
另一个难点是,如何识别文件名是否乱码,否则会导致正常的不乱码的文件名变为乱码。\n\r\
其它的额外功能暂不考虑。\n\r\
\n\r\
开发的过程中发现需要判断参数是文件还是目录。准备通过反斜杠`\\`或者正斜杠`/`来判断,如果存在斜杠,就是目录,如果不存在斜杠,就是文件名。\n\r\
这样判断是不行的,因为如果文件名写绝对路径或相对路径时,也是会带有斜杠的。\n\r\
所以只能通过代码获取文件信息来判断。\n\r\
先判断文件是否存在,不存在结束。如果存在,就判断是文件名还是目录。\n\r\
发现GetFileAttributes的返回值就能直接判断了, - 1表示不存在,16表示目录,其它表示文件。\n\r\
\n\r\
如何区别文件名是否乱码还是比较有难度的。因为要识别编码是gbk还是utf8,这项工作的难度已经大于本程序了。\n\r\
通常情况下,gdk编码比utf8小,因为gbk只支持中文,utf8支持全世界文字。\n\r\
所以可以通过比较大小来简单判断,如果转换后编码体积变小,就转换,如果转换后体积没有变小,则不转换。以免正常的gbk编码被转成乱码,导致无法恢复。\n\r\
这样子还是会有问题,因为utf8转为gbk后,如果继续把gbk编码当作utf8转为gbk,体积还是有可能变小,还是能继续转为乱码。\n\r\
所以暂时要对用户提要求了,要求操作的文件夹里面必须都是中文乱码的,否则后果自负。\n\r\
其实正常情况下,从linux传文件到windows,中文是一定100 % 乱码的,英文是不会乱码的,而转换的过程中,也只是将中文乱码转为中文,英文无论是gbk,还是utf8,怎么互转都不会乱码。\n\r\
这样需求已实现,可是我还是想再实现一个递归操作。该目录下所有子目录的文件都修复。\n\r\
递归想起来难,实现起来还是比较容易的。\n\r\
测试递归的过程中发现问题,需要把目录名先修复,否则后面是按修复后的目录来重命名,这样会导致找不到目录而修改失败。\n\r\
先修复目录名也会有问题,因为目录名修复后,后面再次修复就变成乱码了。\n\r\
可是为什么第一次测试里面的目录不会乱码呢?\n\r\
不知道为什么第一次成功了,现在想重现都不容易重现。\n\r\
正确的做法应该是计算偏移,记录修改后的目录名称,不能再次修改目录名。\n\r\
成功了。\n\r\
如果还有bug靠用户了。\n\r\
!!!这个不要随意乱用,否则可能会将系统造成大麻烦。因为递归操作可以大量将正常的中文改为乱码。\n\r\
考虑到安全性可能比功能更重要,所以准备修改为,如果万一将一个正常的中文文件名改为乱码文件名,并且乱码导致更名失败一次,就结束进程。\n\r\
开发程序时的思维历程和最后输出的源代码同样重要,所以我将思维历程和源代码放一起,并编译到可执行文件里。 \n\r\
最后直接拿可执行文件测试时发现问题,递归修改目录时,里面的文件会跑出来。\n\r\
原因是目录结尾缺少`\\`或`/`,导致目录直接和里面的文件名连在一起出错。加一个判断改善就好了。\n\r\
而且现在windows能同时支持目录里的`\\`或`/`,还是比较方便的。\n\r\
"};