判断文件为文本文件还是二进制文件(C语言实现)
先明确需求:用C
语言实现判断文件是文本文件还是二进制文件,或者其他压缩格式文件。
文件的类型
Linux
系统下,万物皆文件。
为了将所有的东西都能当成文件来管理,Linux
系统将文件分成了七种类型,分别如下:
类型 | 简写 | S_IFMT |
st_mode |
说明 |
---|---|---|---|---|
块设备 | b |
S_IFBLK |
S_ISBLK(m) |
系统存取数据的接口设备,例如硬盘 |
字符设备 | c |
S_IFCHR |
S_ISCHR(m) |
串行端口的接口设备,例如键盘、鼠标、打印机、tty 终端 |
目录 | d |
S_IFDIR |
S_ISDIR(m) |
文件夹 |
链接文件 | l |
S_IFLNK |
S_ISLNK(m) |
符号链接,分软链接和硬链接 |
套接字 | s |
S_IFSOCK |
S_ISSOCK(m) |
用于网络通信 |
普通文件 | - |
S_IFREG |
S_ISREG(m) |
分纯文本文件和二进制文件 |
命名管道 | p |
S_IFIFO |
S_ISFIFO(m) |
命名管道文件 |
Linux
下使用stat
函数判断文件类型提供的一些宏定义。如判断一个文件是否属于普通文件,可以使用下面的代码:stat(pathname, &sb);
if ((sb.st_mode & S_IFMT) == S_IFREG) {
/* Handle regular file */
}
或者直接使用:
stat(pathname, &sb);
if (S_ISREG(sb.st_mode)) {
/* Handle regular file */
}
但是我们的需求是判断文件是否属于文本文件还是二进制文件。而这两种都属于
S_IFREG
普通文件,因此无法使用上面的方法进行判断。万能的file命令
file
命令是Linux
下用来检测文件类型的一个内置的命令。大概原理就是读取一个文件的前面
1024
个字节,然后根据magic
(/etc/magic
或者 /usr/share/misc/magic
) 里对应的规则分析出文件头,并打印到屏幕上。使用也很简单,直接
file
后面跟上文件名即可:[root@ck08 ~]# file anaconda-ks.cfg
anaconda-ks.cfg: ASCII text
[root@ck08 ~]# file tls.pcap
tls.pcap: tcpdump capture file (little-endian) - version 2.4 (Ethernet, capture length 262144)
[root@ck08 ~]# file zlib-1.2.11.tar.gz
zlib-1.2.11.tar.gz: gzip compressed data, was "zlib-1.2.11.tar", from Unix, last modified: Mon Jan 16 01:36:58 2017, max compression
[root@ck08 ~]# file /usr/bin/grep
/usr/bin/grep: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=bb5d89868c5a04ae48f76250559cb01fae1cd762, stripped
从上面的示例中,可以看出
file
命令很强大,几乎可以识别出文件的详细类型,甚至是编码,压缩格式,大小端等具体的信息。因此,这个命名是符合我们的需求的。
但是,我们需要的是
C
语言实现,因此,不得不研究magic
的文件头规则。magic文件规则
文件中的每行都指定了一个规则测试去检验文件类型,这个规则由
4
个域指定。分别为offset
、type
、test
、message
。offset
- 指定由文件起始的第几个
byte
开始检验。
- 指定由文件起始的第几个
type
- 要进行检验的数据类型,即由
offset
那个byte
开始的那个数据类型是什么。具体有哪些数据类型,可以参考magic(5)
。常用的数据类型有
byte
:一个byte
的值short
:两个byte
的值long
:四个byte
的值string
:字符串
- 要进行检验的数据类型,即由
test
- 检验值。用于检验
offset
下的type
是否是这个test
值。使用C
语言的数值或字符表示形式。
- 检验值。用于检验
message
- 用于显示检验结果的信息显示。
type
为数值类型,那么其后面可添加&value
,表示先与后面的test
值进行'与'操作,再进行比较。如果type
为字符串类型,则其后可跟/[Bbc]*
,/b
表示忽略空格,/c
表示忽略字母大小写。
如果test
的值为数值类型,可以数值前添加=
,<
,>
,&
,^
,~
,分别表示相等、小于、大于、与操作、异或操作、取反操作。
如果test
的值为字符串类型,可以在其前添加=
、<
、>
。
例如,ELF文件的magic表示为:
# ELF #0stringELFELF 0string\177ELFELF >4byte132-bit >4byte264-bit >5byte1LSB >5byte2MSB >16short0unknown type >16short1relocatable >16short2executable >16short3dynamic lib >16short4core file >18short0unknown machine >18short1WE32100 >18short2SPARC >18short380386 >18short4M68000 >18short5M88000 >20long1Version 1 >36long1MathCoPro/FPU/MAU Required
magic
实在不是人类能够轻易读懂的,Linux
内核提供了libmagic
库用来解析magic
文件,但是我尝试了CentOS 7
和Ubuntu20.04
,都没有成功将程序跑起来(gcc
编译报告找不到magic.h
),而我的需求是要求一种比较通用的方法,不仅要求在Linux
上可以工作,还要在Windows
和AIX
上有比较好的表现,因此,企图实现一套类似file
的原理的路行不通。那么,就没有一种比较通用的方案来实现对文件类型的判断吗?
网上很多资料说可以根据文件的字符来判断,如果文件中包含
\x00
,则一定是二进制或压缩文件,否则的话就是普通文本文件。在大多数时候,这个规则是成立的。但是如果普通文本文件的编码是
UTF-16
或者UTF-32
,则又要哭晕在厕所了。因此,这种方案不靠谱。
特殊文件的header
libmagic
的思路,说白了,就是根据文件头的编码进行判断,也就是说,只要我们知道某些特殊的文件头编码,对这些特殊的文件头进行匹配,如果能匹配上,就代表它是特殊文件,否则的话,就是普通文本文件,按照这个思路,也能实现libmagic
库一样的效果。在各种类型文件头标准编码这篇文章里,列举了一些常见的文件头编码。比如常见的
jar
包、rar
、zip
压缩文件,都是以504B0304
开头,而Linux
下的二进制文件,包括.o
,.a
,.so
,以及coredump
文件,都是属于ELF
文件,文件头都是7F454C46
。但是windows
的可执行文件开头却是504B0304
,AIX
系统复杂些,但开头三个字节基本都是01DF00
。因此,根据这些,就可以做很多区分了。事实上,对于
windows
系统来说,根据后缀其实就能区分出来,而Unix
系统的约定俗成的后缀规则也能区分出很多的文件,比如一个文件后缀为.rpm
,你无论如何不会将其当成文本文件,看到.o
就知道是二进制目标文件,.so
是动态链接库。比较有歧义的可能只是一些可执行文件,比如ls
、grep
、a.out
这些后缀不代表实际意义的文件。因此,我们的思路也就明确了,分两个步骤,首先可以大致根据文件后缀区分出一些特别明显的二进制文件、压缩文件,然后针对文件的
header
做进一步的区分。C语言代码实现 代码实现如下:
#include
#include
#include typedef int boolean;
#define FALSE 0
#define TRUE1/*列举一些常见的文件头,可以自行扩展,比如放到某个配置文件中*/
static const char *with_suffix[] = {".gz", ".rar", ".exe", ".bz2",
".tar", ".xz", ".Z", ".rpm", ".zip",
".a",".so", ".o", ".jar", ".dll",
".lib", ".deb", ".I", ".png",".jpg",
".mp3", ".mp4", ".m4a", ".flv", ".mkv",
".rmvb", ".avi",".pcap", ".pdf", ".docx",
".xlsx", ".pptx", ".ram", ".mid", ".dwg",
NULL};
/*判断某个字符串是否拥有指定后缀*/
static boolean string_has_suffix(const char *str, const char *suffix) {
int n, m, i = 0;
char ch = '\0';
if (str == NULL || suffix == NULL)
{
return FALSE;
}
n = strlen(str);
m = strlen(suffix);
if (n < m) {
return FALSE;
}
for (i = m-1;
i >= 0;
i--) {
if (suffix[i] != str[n - m + i]) {
return FALSE;
}
}
return TRUE;
}/*判断文件是否具有特殊后缀*/
static boolean file_has_spec_suffix(const char *fname) {
const char **suffix = NULL;
suffix = with_suffix;
while (*suffix)
{
if (string_has_suffix(fname, *suffix))
{
return TRUE;
}
suffix++;
}
return FALSE;
}/*判断文件是否具有特殊文件头*/
static boolean file_has_spec_header(const char *fname) {
FILE *fp = NULL;
size_t len = 0;
char buf[16] = {0};
int i = 0;
boolean retval = FALSE;
if ((fp = fopen(fname, "r")) == NULL ){
return FALSE;
}len = sizeof(buf) - 1;
if (fgets(buf, len, fp) == NULL ){
return FALSE;
}
if (len < 4)
{
return FALSE;
}
#if defined(__linux__)
//ELF header
if (memcmp(buf, "\x7F\x45\x4C\x46", 4) == 0) {
return TRUE;
}
#elif defined(_AIX)
//executable binary
if (memcmp(buf, "\x01\xDF\x00", 3) == 0) {
return TRUE;
}
#elif defined(WIN32)
// standard exe file, actually, won't go into this case
if (memcmp(buf, "\x4D\x5A\x90\x00", 4) == 0)
{
return TRUE;
}
#endif
if (memcmp(buf, "\x50\x4B\x03\x04", 4) == 0) {
//maybe archive file, eg: jar zip rar sec.
return TRUE;
}return FALSE;
}/*测试程序
* 从命令行输入一个文件,返回该文件的类型
*/
int main(int argc, const char **argv) {
if (argc < 2) {
printf("usgae: need target file\n");
exit(-1);
}
const char *fname = argv[1];
if (file_has_spec_suffix(fname)) {
printf("file %s have special suffix, maybe it's a binary or archive file\n", fname);
} else if (file_has_spec_header(fname)) {
printf("file %s have special header, maybe it's a binary or archive file\n", fname);
} else {
printf("file %s should be a text file\n", fname);
}
return 0;
}
【判断文件为文本文件还是二进制文件(C语言实现)】运行结果如下所示,可以对比file命令,做一个参考:
[root@ck08 ctest]# gcc -o magic magic.c
[root@ck08 ctest]# ./magic ~/anaconda-ks.cfg
file /root/anaconda-ks.cfg should be a text file
[root@ck08 ctest]# ./magic ~/tls.pcap
file /root/tls.pcap have special suffix, maybe it's a binary or archive file
[root@ck08 ctest]# ./magic ~/zlib-1.2.11.tar.gz
file /root/zlib-1.2.11.tar.gz have special suffix, maybe it's a binary or archive file
[root@ck08 ctest]# ./magic /usr/bin/grep
file /usr/bin/grep have special header, maybe it's a binary or archive file
[root@ck08 ctest]# ./magic kafka_2.12-2.8.0.jar
file kafka_2.12-2.8.0.jar have special suffix, maybe it's a binary or archive file
[root@ck08 ctest]# ./magic kafka_2.12-2.8.0.jar.1
file kafka_2.12-2.8.0.jar.1 have special header, maybe it's a binary or archive file
参考文档
- https://linux.die.net/man/5/m...
- https://man7.org/linux/man-pa...
- https://www.cnblogs.com/gwind...
推荐阅读
- appium常用方法整理
- Spring的配置文件applicationContext.xml
- android打包生成apk时自定义文件名版本号。自定义项目字段等等
- Dapper多表查询时子表字段为空
- QQ聊天记录在啥地方个文件夹?QQ聊天记录保存办法
- 手机设置了静音,微信为啥揺一揺还有声音?_微信
- Spring MVC文件上传示例详解
- Docker将镜像文件发布到阿里云
- Nginx的常用配置
- Android 适配(drawable文件夹)图片适配