concat 是一个常见的宏编程技术,用于将两个或多个预处理器标记(通常是宏参数或宏定义)连接(concatenate)成一个单一的标记, 通常通过使用预处理器的 ## 操作符实现,该操作符用于连接两个标记。

`Concat`宏的基本用法

假设有一个 concat 宏定义如下:

1
#define concat(a, b) a ## b

这里,## 操作符会将它的两个参数 a 和 b 连接成一个单一的标记。

示例

使用 concat 来动态创建函数名:

1
2
3
4
5
6
7
8
9
10
11
12
#define concat(a, b) a ## b

// 定义一个示例函数
void func123() {
// 函数体
}

int main() {
// 调用 concat 宏来连接 'func' 和 '123',生成函数名 'func123'
concat(func, 123)();
return 0;
}

在这个例子中,concat(func, 123) 会在预处理阶段被展开为 func123,因此 concat(func, 123)(); 实际上调用的是 func123()函数。

原文

some notes:

返回值: main()的返回值应该是int, 而不是void. 虽然在一些编译器中,void main()可以通过编译,但并非所有编译器都支持 void main(),因为标准中从来没有定义过 void main 。

main返回值类型:
main返回值类型

main()函数传参

1
int main(int argc , char* argv[],char* envp[]); 

参数说明:

①、第一个参数argc表示的是传入参数的个数 。

②、第二个参数char* argv[],是字符串数组,用来存放指向的字符串参数的指针数组,每一个元素指向一个参数。各成员含义如下:

argv[0]:指向程序运行的全路径名。

argv[1]:指向执行程序名后的第一个字符串 ,表示真正传入的第一个参数。

argv[2]:指向执行程序名后的第二个字符串 ,表示传入的第二个参数。

……argv[n]:指向执行程序名后的第n个字符串 ,表示传入的第n个参数。

规定:argv[argc]为NULL ,表示参数的结尾。

③、第三个参数char* envp[],也是一个字符串数组,主要是保存这用户环境中的变量字符串,以NULL结束。envp[]的每一个元素都包含ENVVAR=value形式的字符串,其中ENVVAR为环境变量,value为其对应的值。

envp一旦传入,它就只是单纯的字符串数组而已,不会随着程序动态设置发生改变。可以使用putenv函数实时修改环境变量,也能使用getenv实时查看环境变量,但是envp本身不会发生改变;平时使用到的比较少。

注意:main函数的参数char* argv[]和char* envp[]表示的是字符串数组,书写形式不止char* argv[]这一种,相应的argv[][]和char** argv均可。

对main()函数传参

格式: 可执行文件名 参数1 参数2 … … 参数n
eg:

1
2
ffo@debian:~/Cprogramme$ gcc testmain.c 
ffo@debian:~/Cprogramme$ ./a.out 1 2 3

main()的执行顺序

linux系统下程序的入口是”_start”,这个函数是linux系统库(Glibc)的一部分,当我们的程序和Glibc链接在一起形成最终的可执行文件的之后,这个函数就是程序执行初始化的入口函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
_start:
xor ebp, ebp //清空ebp
pop esi //保存argc,esi = argc
mov esp, ecx //保存argv, ecx = argv

push esp //参数7保存当前栈顶
push edx //参数6
push __libc_csu_fini//参数5
push __libc_csu_init//参数4
push ecx //参数3
push esi //参数2
push main//参数1
call _libc_start_main

hlt

所以在main()函数执行前还要做一系列的工作, 主要是初始化系统相关资源:

1
2
3
4
5
6
7
8
9
10
11
12
13
Some of the stuff that has to happen before main():

set up initial stack pointer

initialize static and global data

zero out uninitialized data

run global constructors

Some of this comes with the runtime library's crt0.o file or its __start() function. Some of it you need to do yourself.

Crt0 is a synonym for the C runtime library.

在main函数执行前的函数:

(1)全局对象的构造函数会在main 函数之前执行。

(2)一些全局变量、对象和静态变量、对象的空间分配和赋初值就是在执行main函数之前,而main函数执行完后,还要去执行一些诸如释放空间、释放资源使用权等操作

(3)进程启动后,要执行一些初始化代码(如设置环境变量等),然后跳转到main执行。全局对象的构造也在main之前。

(4)通过关键字attribute,让一个函数在主函数之前运行,进行一些数据初始化、模块加载验证等。

作者:z_ryan

原文:https://blog.csdn.net/z_ryan/category_7316855.html

免责声明:本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。

在读nemu/src/monitor/monitor.c中遇到了这个函数,原文如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static inline void parse_args(int argc, char *argv[]) {
const struct option table[] = {
{"batch" , no_argument , NULL, 'b'},
{"log" , required_argument, NULL, 'l'},
{"diff" , required_argument, NULL, 'd'},
{"port" , required_argument, NULL, 'p'},
{"help" , no_argument , NULL, 'h'},
{0 , 0 , NULL, 0 },
};
int o;
while ( (o = getopt_long(argc, argv, "-bhl:d:p:", table, NULL)) != -1) {
switch (o) {
case 'b': batch_mode = true; break;
case 'p': sscanf(optarg, "%d", &difftest_port); break;
case 'l': log_file = optarg; break;
case 'd': diff_so_file = optarg; break;
case 1:
if (img_file != NULL) Log("too much argument '%s', ignored", optarg);
else img_file = optarg;
break;
default:
printf("Usage: %s [OPTION...] IMAGE\n\n", argv[0]);
printf("\t-b,--batch run with batch mode\n");
printf("\t-l,--log=FILE output log to FILE\n");
printf("\t-d,--diff=REF_SO run DiffTest with reference REF_SO\n");
printf("\t-p,--port=PORT run DiffTest with port PORT\n");
printf("\n");
exit(0);
}
}
}

这个函数中使用了getopt_long()函数, 在man 3 getopt并查阅资料后,对这个函数有了初步的了解
#

概要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <unistd.h>

int getopt(int argc, char *const argv[],
const char *optstring);

extern char *optarg;
extern int optind, opterr, optopt;

#include <getopt.h>

int getopt_long(int argc, char *const argv[],
const char *optstring,
const struct option *longopts, int *longindex);
int getopt_long_only(int argc, char *const argv[],
const char *optstring,
const struct option *longopts, int *longindex);

char *optarg:指向当前选项参数(如果有)的指针。

int optind:下一次调用 getopt() 时的 argv 索引的起始位置。

int opterr:如果非零,getopt() 会在遇到错误时打印错误消息。如果把opterr设置为0, 那么getopt()就不会自动错误报告

getopt()其实是一个提供解析命令行参数的函数,getopt()函数使得程序能够以一种标准的方式来解析命令行选项(如 -a--option 这样的短选项或长选项)。

函数原型

1
int getopt(int argc, char * const argv[], const char *optstring); 

argc argv[]:这两个参数直接来自于程序的 main() 函数,代表命令行参数的数量和参数的字符串数组。
optstring:一个包含有效选项字符的字符串。如果一个选项字符后面跟随一个冒号(:),那么表示该选项需要一个参数(例如,-f filename 中的 filename 就是 -f 选项的参数)。 eg: optstring = “abc:d” ,那么说明一共有四个选项-a、-b、-c、-d, 其中c后面跟一个冒号,说明在使用-c选项的时候需要提供一个参数。

使用方式

getopt() 函数在每次调用时都会返回命令行中的下一个选项字符,直到所有选项都被处理完毕,此时返回 -1。处理选项时,如果遇到未定义的选项字符,getopt() 会打印一个错误消息到标准错误并返回 ?。如果选项需要一个参数但未提供参数,根据 optstring 的第一个字符是否为 :,getopt() 返回 ? 或 : 来区分错误类型。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
int opt;

while ((opt = getopt(argc, argv, "if:lr")) != -1) {
switch (opt) {
case 'i':
case 'l':
case 'r':
printf("Option: %c\n", opt);
break;
case 'f':
printf("Filename: %s\n", optarg);
break;
case '?':
fprintf(stderr, "Usage: %s [-ilr] [-f filename] args\n", argv[0]);
exit(EXIT_FAILURE);
}
}

for (int index = optind; index < argc; index++)
printf("Non-option argument: %s\n", argv[index]);

return 0;
}

这里提供了四个选项-i、-f、-l、-r, 其中使用-f选项的时候需要提供一个参数, 如果提供的选项没有定义,就会打印Non-option argument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ffo@debian:~/Cprogramme$ vim test_getopt.c 
ffo@debian:~/Cprogramme$ gcc test_getopt.c
ffo@debian:~/Cprogramme$ ./a.out -i
Option: i
ffo@debian:~/Cprogramme$ ./a.out -f "1121"
Filename: 1121
ffo@debian:~/Cprogramme$ ./a.out -a
./a.out: invalid option -- 'a'
Usage: ./a.out [-ilr] [-f filename] args
ffo@debian:~/Cprogramme$ ./a.out -f
./a.out: option requires an argument -- 'f'
Usage: ./a.out [-ilr] [-f filename] args
ffo@debian:~/Cprogramme$ ./a.out f
Non-option argument: f

由上可以看出getopt()其实就是对命令行参数解析的函数, 同时也可以解析多个选项,like this:

1
2
3
4
5
ffo@debian:~/Cprogramme$ ./a.out -i -f "1121" -a
Option: i
Filename: 1121
./a.out: invalid option -- 'a'
Usage: ./a.out [-ilr] [-f filename] args

我们也可以修改源文件查看optind, oprerr的值:
在打印选项之前添加一行代码:

1
printf("optind: %d ", optind);

在打印错误消息前添加一行

1
printf("optind: %d opterr: %d ", optind, opterr);

再次执行得到:

1
2
3
4
5
6
 ./a.out -i -l -f
optind: 2 Option: i
optind: 3 Option: l
./a.out: option requires an argument -- 'f'
Usage: ./a.out [-ilr] [-f filename] args
optind: 4 opterr: 1

其中有个小问题,我们明明在打印错误消息前先打印了optind,opterr的值,但是为什么记过出来之后却是先打印错误消息在打印这两个变量的值, 这是因为当遇到需要参数但未提供参数的选项(例如 -f)时,getopt 函数的行为会因 opterr 的值而有所不同。在大多数环境中,默认情况下 opterr 被设置为 1,这意味着 getopt 会自动打印错误信息到标准错误输出(stderr)。

这条消息是由 getopt 函数自动产生的,因为 -f 选项缺少必需的参数。在打印了错误消息之后,getopt 函数返回 ‘?’,导致程序执行 case ‘?’ 分支。在这个分支中,尝试打印 optind 和 opterr 的值。

但是,由于错误消息已经被 getopt 自动打印出来了(在进入 case ‘?’ 分支之前),所以看起来像是 Usage 信息和 optind: 2 opterr: 1 的打印顺序颠倒了。

getopt_long()

1
2
3
4
5
6
7
#include <getopt.h>

int getopt_long(int argc, char * const argv[],
const char *optstring,
const struct option *longopts,
int *longindex);

这个函数比getopt()多加了两个参数, 但是原先的参数还是同样的作用。

const struct option *longopts: 指向 option 结构的数组,每个元素定义了一个长选项,包括选项的名称、是否需要参数、一个返回值的指针和短选项字符。数组的最后一个元素必须是全零,作为终结符。

int *longindex: 如果非 NULL,getopt_long 将在此处存储 longopts 数组中当前找到的选项的索引。

option 结构定义

1
2
3
4
5
6
struct option {
const char *name; // 长选项名称
int has_arg; // 选项是否需要参数:no_argument(0)、required_argument(1)或 optional_argument(2)
int *flag; // 如果非 NULL,getopt_long 返回 0,而 *flag 设置为 val 的值;如果 NULL,getopt_long 返回 val
int val; // 与选项相关的值;如果 flag 非 NULL,val 是 *flag 的值,否则 val 是 getopt_long 的返回值
};

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

int main(int argc, char *argv[]) {
int c;
int digit_optind = 0;

static struct option long_options[] = {
{"add", required_argument, 0, 'a'},
{"append", no_argument, 0, 'b'},
{"delete", required_argument, 0, 'd'},
{"verbose", no_argument, 0, 'v'},
{"create", required_argument, 0, 'c'},
{"file", required_argument, 0, 'f'},
{0, 0, 0, 0}
};

while (1) {
int option_index = 0;

c = getopt_long(argc, argv, "abc:d:vf:", long_options, &option_index);

if (c == -1)
break;

switch (c) {
case 'a':
case 'b':
case 'v':
printf("Option %c\n", c);
break;
case 'c':
case 'd':
case 'f':
printf("Option %c with argument '%s'\n", c, optarg);
break;
case '?':
break;
default:
printf("?? getopt returned character code 0%o ??\n", c);
}
}

if (optind < argc) {
printf("Non-option ARGV-elements: ");
while (optind < argc)
printf("%s ", argv[optind++]);
printf("\n");
}

exit(EXIT_SUCCESS);
}

执行后会发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ffo@debian:~/Cprogramme$ ./a.out -a
Option a
ffo@debian:~/Cprogramme$ ./a.out -f "12121"
Option f with argument '12121'
ffo@debian:~/Cprogramme$ ./a.out -add
Option a
Option d with argument 'd'
ffo@debian:~/Cprogramme$ ./a.out --add
./a.out: option '--add' requires an argument
ffo@debian:~/Cprogramme$ ./a.out --add "12121"
Option a
ffo@debian:~/Cprogramme$ ./a.out --verbose
Option v
ffo@debian:~/Cprogramme$

现在我们无论用短选项还是长选项都可以正确解析。

开天辟地的篇章

从状态机视角理解程序运行

1
2
3
4
5
6
7
// PC: instruction    | // label: statement
0: mov r1, 0 | pc0: r1 = 0;
1: mov r2, 0 | pc1: r2 = 0;
2: addi r2, r2, 1 | pc2: r2 = r2 + 1;
3: add r1, r1, r2 | pc3: r1 = r1 + r2;
4: blt r2, 100, 2 | pc4: if (r2 < 100) goto pc2; // branch if less than
5: jmp 5 | pc5: goto pc5;

Q:画出这个程序的状态机

需要更新的状态只包括PC,r1和r0,所以用三元组表示程序的所有状态:

(0, x, x) -> (1, 0, x) -> (2, 0, 0) -> (3, 0, 1) -> (4, 1, 1) -> (2, 1, 1) -> (3, 1, 2) -> (4, 3, 2) -> … -> (2, 4851, 98) -> (3, 4851, 99) -> (4, 4950, 99) -> (2, 4950, 99) -> (3, 4950, 100) -> (4, 5050, 100) -> (5, 5050, 100)

  • Thoughts : 如果不是实验中给出了初始的(0, x, x) -> (1, 0, x) -> (2, 0, 0) -> .. , 以我的感觉我极有可能会写(0, 1, x) -> (1, 0, 0) -> .. 我的逻辑是在pc0的时候,r1赋值为0了, 但是在状态机中表示的是一个瞬间的状态所以在pc0的时候, r1还没有被赋值,所以上面的(0, x, x)是对的

RTFSC

函数: getopt()

变量: _IMAGE_START_: 客户程序的固定内存位置

x86寄存器结构实现:

原题:

1
2
3
4
5
6
* TODO: Re-organize the `CPU_state' structure to match the register                                 │
* encoding scheme in i386 instruction format. For example, if we │
* access cpu.gpr[3]._16, we will get the `bx' register; if we access │
* cpu.gpr[1]._8[1], we will get the 'ch' register. Hint: Use `union'. │
* For more details about the register encoding scheme, see i386 manual. │
*/

题目很明显需要我们将RTL寄存器与通用寄存器(GPRs)联系起来(很尴尬的是刚开始我并没有看到这段话T_T), 并提示我们使用union来实现。

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct {

struct {
uint32_t _32;
uint16_t _16;
uint8_t _8[2];
} gpr[8];

/* Do NOT change the order of the GPRs' definitions. */

/* In NEMU, rtlreg_t is exactly uint32_t. This makes RTL instructions
in PA2 able to directly access these registers.
*/
rtlreg_t eax, ecx, edx, ebx, esp, ebp, esi, edi;

vaddr_t pc;
} x86_CPU_state;

那么有了提示思路就很明确, 我们只需要把gpr[]数组跟各个rtlreg共享内存位置即可。

于是我首先是这样构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
union {
struct {
uint32_t _32;
uint16_t _16;
uint8_t _8[2];
} gpr[8];

struct {
rtlreg_t eax;
rtlreg_t edx;
rtlreg_t ecx;
rtlreg_t ebx;
rtlreg_t ebp;
rtlreg_t esi;
rtlreg_t edi;
rtlreg_t esp;
};
};

很明显的,gpr[]数组的跟RTLREG结构体共享同一段地址,我们对gpr[]每个元素的修改也会直接影响到RTLREG的值。

但是,测试不通过

我很奇怪,但是找不到任何问题,在我不知道怎么修改的乱修改之后发现只要把gpr[]数组也改成union联合体就可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
union {
union {
uint32_t _32;
uint16_t _16;
uint8_t _8[2];
} gpr[8];

struct {
rtlreg_t eax;
rtlreg_t edx;
rtlreg_t ecx;
rtlreg_t ebx;
rtlreg_t ebp;
rtlreg_t esi;
rtlreg_t edi;
rtlreg_t esp;
};
};

从整体看,我并没有找到问题, 两段代码都是对rtlreg和gpr[]数组的内存地址统一,从功能上来看都是相同的。

答案就是, 在第一段代码中gpr[]是一个结构体代码,那么就意味着在结构体中的变量是一段连续内存地址中相互独立的:每个成员占有独立的内存空间, _32, _16, _8[2]都是独立的内存地址, 而这显然不符合寄存器的结构, 在i386手册中,通用寄存器是这么定义的:

PA1-i386通用寄存器内存地址

那么在第二段代码中_32, _16, _8[2]本质是共享了同一段内存地址,这意味着对其中任一成员的修改都会影响到同一内存位置的其他成员。

一些奇怪的问题

在上面图片中可以看到,RTLREG的顺序跟我的结构体中的顺序并不相同,但是却可以正常运行, 但是当我按照图片上的顺序修改之后却无法运行

Linux 命令总结

文件管理

  • cd <directory>: 更改当前工作目录到指定的目录。
  • pwd: 显示当前工作目录的完整路径。
  • mkdir <directory>: 创建一个新目录。
  • rmdir <directory>: 删除一个空目录。如果目录非空,使用rm -r <directory>
  • ls [options] [file]: 列出目录内容。
    • -l: 长格式列出信息。
    • -a: 列出所有文件,包括隐藏文件。
    • -h: 与-l一起使用时,以易读的方式显示文件大小。
  • cp [options] <source> <destination>: 复制文件或目录。
    • -r--recursive: 递归复制目录及其内容。
    • -i--interactive: 在覆盖文件之前提示用户确认。
    • -v--verbose: 显示详细信息。
  • rm [options] <file>: 删除文件或目录。
    • -r--recursive: 递归地删除目录及其内容。
    • -f--force: 强制删除,不提示确认。
  • mv [options] <source> <destination>: 移动或重命名文件或目录。
  • tar [options] <filename> [files]: 用于归档文件,同时可对文件进行压缩或解压。
    • -c: 创建归档。
    • -x: 从归档中提取文件。
    • -z: 通过gzip进行压缩或解压。
    • -v: 显示被处理的文件名。
    • -f: 指定归档文件的名称。

文件检索

  • cat <file>: 显示文件内容。
  • more <file>: 分页显示文件内容。
  • less <file>: 类似于more,但允许反向导航。
  • head [options] <file>: 显示文件的开头部分。
  • tail [options] <file>: 显示文件的结尾部分。
  • file <file>: 确定文件类型。
  • find [path] [expression]: 搜索文件和目录。
    • -name: 根据文件名搜索。
    • -type: 指定文件类型进行搜索。
    • -exec: 对搜索结果执行指定的命令。

输入输出控制

  • 重定向 (><): 将命令的输出重定向到文件或从文件中读取输入。
  • 管道 (|): 将一个命令的输出作为另一个命令的输入。
  • tee <file>: 读取标准输入的数据,并同时写入文件和标准输出。
  • xargs: 构造参数列表并执行命令。

文本处理

  • vim <file>: 打开Vim文本编辑器以编辑文件。
  • grep [options] <pattern> [file]: 搜索文本并打印匹配行。
    • -i: 忽略大小写差异。
    • -r--recursive: 递归搜索子目录中的文件。
    • -v: 反转匹配。
  • awk [program] [file]: 强大的文本处理工具,用于模式扫描和处理。
  • sed [options] [script] [file]: 流编辑器,用于处理文本流。
  • sort [options] [file]: 对文件中的行进行排序。
  • wc [options] [file]: 统计文件的行数、字数和字节数。
  • uniq [options] [input] [output]: 报告或省略重复行。
  • cut [options] [file]: 从文件的每一行剪切字节、字符和字段。
  • tr [options] <set1> <set2>: 替换或删除字符。

系统监控

  • jobs: 列出当前会话中的所有任务。
  • ps [options]: 显示当前进程的快照。
  • top: 显示实时运行系统的进程信息。
  • kill [options] <pid>: 发送信号到一个或多个进程。
  • free: 显示内存的使用情况。
  • dmesg: 显示或控制内核环形缓冲区的内容。
  • lsof [options]: 列出被进程打开的文件。

前几天在做蒋炎岩老师的PA有一个小实验在实验中使用g++ main.cpp -o main来编译cpp文件, 但是上面的-o其实是g++的一个选项, 作用是自定义生成文件的名称, 上面的指令就是编译main.cpp文件, 并将编译后的可执行文件命名为main.

所以编译c文件其实只需要gcc main.c指令, 这条指令会默认生成a.out可执行文件

我突然想到一个问题, 是不是上面的main也是.out文件, 但是在我查阅资料后发现并不是这样:

1
g++ main.cpp -o hello

使用-o选项是可以自定义可执行文件的名称,这里生成的是hello,注意文件的拓展名并不是.out

文件的拓展名对linux来说并不重要, 可执行文件的性质由其权限和格式决定.

文件权限

在Linux和其他类Unix系统中,文件权限控制着文件的可访问性。这些权限指定了哪些用户可以读取、写入或执行某个文件。如果一个文件被标记为可执行(通常通过设置执行权限位,例如使用chmod +x filename命令),那么系统的用户(根据文件的权限设置)就可以尝试执行这个文件。

文件格式

可执行文件需要有特定的格式,最常见的是ELF(Executable and Linkable Format)格式。这个格式告诉操作系统如何加载程序到内存中并执行它。只有符合系统预期的可执行格式的文件才能被作为程序运行。即使一个文件具有执行权限,如果它不是有效的可执行格式,操作系统也无法执行它。

文件名

文件是否可以执行与其名字无关。你可以把一个可执行文件重命名为任何名字,只要它保持了执行权限和有效的可执行格式,它就仍然是可执行的。例如,你可以将一个编译好的程序从program重命名为program.out或任何其他名字,这不会影响其作为可执行文件的能力.

文件扩展名的作用

用户提示:文件扩展名帮助用户和应用程序理解文件的预期用途和内容类型。例如,.txt通常表示纯文本文件,.png表示PNG图像文件。
应用程序关联:许多图形用户界面(GUI)环境和应用程序使用文件扩展名来判断如何打开特定的文件。例如,双击一个.pdf文件通常会打开一个PDF阅读器应用程序。
命令行工具:一些命令行工具可能也会根据文件扩展名来处理文件,尽管这不是强制的。例如,编译器如gcc不会根据源代码文件的扩展名来改变其行为,但它默认接受.c作为C语言源代码文件的扩展名。

Linux如何确定文件类型

Linux使用几种方法来确定如何处理文件,不仅仅依靠文件扩展名:
文件权限:如之前讨论的,文件的权限(特别是执行权限)决定了用户是否可以执行该文件。
文件内容:Linux可以通过文件的前几个字节(称为“魔数(magic number)”)来识别文件的类型。例如,可执行文件、图像文件等都有特定的开头字节序列。
解释器指令行:对于脚本文件(如.sh、.py等),第一行通常包含一个称为shebang(#!)的指令,指明了执行该脚本时应使用的解释器路径。这允许直接执行脚本文件,而不依赖于文件的扩展名。

0%