Linux Shell ERROR/RETURN code

理解Shell退出码
本文详细解释了Shell脚本中各种退出码的意义,包括通用错误、命令执行失败等常见情况,并提出了标准化退出码的建议。
  Exit Code Number  Meaning Example Comments

  1             Catchall for general errors let “var1 = 1/0″  Miscellaneous errors, such as ”divide by zero” and other impermissible operations

  2             Misuse of shell builtins (according to Bash documentation)  empty_function() {} Seldom seen, usually defaults to exit code 1
  126         Command invoked cannot execute    Permission problem or command is not an executable
  127        “command not found” illegal_command Possible problem with $PATH or a typo
  128         Invalid argument to exit  exit 3.14159  exit takes only integer args in the range 0 – 255 (see first footnote)
  128+n    Fatal error signal ”n”  kill -9 $PPID of script $? returns 137 (128 + 9)
  130        Script terminated by Control-C    Control-C is fatal error signal 2, (130 = 128 + 2, see above)

  255*      Exit status out of range  exit -1 exit takes only integer args in the range 0 – 255


退出码的值 
1 通用错误  let "var1 = 1/0"  各种各样的错误都可能使用这个退出码, 比如"除0错误"
2 shell内建命令使用错误(Bash文档上有说明)   很少看到, 通常情况下退出码都为1
126 命令调用不能执行    程序或命令的权限是不可执行的
127 "command not found"   估计是$PATH不对, 或者是拼写错误
128 exit的参数错误  exit 3.14159  exit只能以整数作为参数, 范围是0 - 255(见脚注)
128+n 信号"n"的致命错误 kill -9 脚本的$PPID $? 返回137(128 + 9)
130 用Control-C来结束脚本   Control-C是信号2的致命错误, (130 = 128 + 2, 见上边)
255*  超出范围的退出状态  exit -1 exit命令只能够接受范围是0 - 255的整数作为参数
通过上面的表, 我们了解到, 退出码1 - 2, 126 - 165, 和255 [1] 都具有特殊的含义, 因此应该避免使用用户指定的退出参数. 如果脚本使用exit 127作为退出语句, 那么可能就会在故障诊断的时候产生混淆(如何判断这是由"command not found"引起的, 还是由用户定义引起的?). 然而, 许多脚本使用exit 1作为通用的返回错误值. 因为退出码1能够表示的错误太多了, 不过这么做, 对于调试来说, 也起不到任何帮助的作用.
其实早就有人对退出状态值进行了系统的分类(请参考/usr/include/sysexits.h), 不过这个文件是为C/C++程序员准备的. 其实shell脚本也需要这样一个类似的标准. 所以本文作者呼吁限制使用用户定义的退出码, 尤其是范围64 - 113(还有
0, 表示成功), 这么做, 就可以和C/C++标准保持一致. 这样我们就有了50个可用的退出码, 而且非常便于故障诊断.
本书中所有例子中的用户定义退出码都符合这个标准, 除了那些超出标准范围的例子, 比如例子 9-2.


  只有在Bash或sh提示符下, 当shell脚本退出后, 在命令行上使用$?才会得到与上表相一致的结果. 在某些情况下, 运行C-shell或者tcsh可能会给出不同的值.
  注意事项
  [1]
  超出范围的退出值可能会产生意想不到的退出码. 如果退出值比255大, 那么退出码将会取256的模. 举个例子, exit 3809的退出码将是225(3809 % 256 = 225).


退出码的值

 1    通用错误 let "var1 = 1/0" 各种各样的错误都可能使用这个退出码, 比如"除0错误"

 2    shell内建命令使用错误(Bash文档上有说明) 很少看到, 通常情况下退出码都为1。所有内建命令返回 2 来指示不正确的用法。

 126   命令调用不能执行 程序或命令的权限是不可执行的

 127   "command not found" 估计是$PATH不对, 或者是拼写错误

 128   exit的参数错误 exit 3.14159 exit只能以整数作为参数, 范围是0 - 255

 128+n 信号"n"的致命错误 kill -9 脚本的$PPID $? 返回137(128 + 9)

 130   用Control-C来结束脚本 Control-C是信号2的致命错误, (130 = 128 + 2, 见上边)

 255+  超出范围的退出状态 exit -1 exit命令只能够接受范围是0 - 255的整数作为参数。如果退出值比255大, 那么退出码将会取256的模. 举个例子, exit 3809的退出码将是225(3809 % 256 = 225).

返回值分析

   1:程序中,用 exit 来设置进程的退出值时,虽然该参数的类型为 int,但在父进程中,只能取得其值的低 8 位,所以用 exit 返回时,高于 255 是没有意义的。
   2:对应 system 返回值,其值由两部分组成:低 8 位表示所执行的脚步在执行过程中所接收到的信号值,其余的位表示 exit 的返回值。
   3:由 2 可知,对于 system 返回值(ret)来讲,ret>>8 即为 exit 返回值,也对应为获取的 shell 命令的执行状态($?)。
   4:return : Perl 与 Shell(Bash)是不同的

      Bash只能返回0-255的值,但可以通过echo返回字符串和整数

      Perl 可以通过return返回字符串,数字(整数和小数)


system, 反引号,qx

1: 反勾号返回的是命令行返回的print的数值和出错代码,标准输出不会输出到标准输出。

2: system这个函数为你执行任何系统里的程序并返回该程序的退出状态——而不是它的输出。要捕获命令行上的输出,你应该用反勾号或者 qx//。system的输出直接输出到标准输出上。

3: qx// 返回的是命令行返回的标准输出,不含标准错误, 退出代码在 $? 中,但和 终端 中的退出代码不同。

4: system, qx//, 反引号 的错误都会输出到标准错误上,不管是不是在管道中。

带有管道的命令组合

1: 命令从左到右依次执行,直到所有命令执行完毕或遇到致命错误。

2: 左边的命令对标准输出上的输出是它所链接的命令的输入。如果没有重定向,标准错误直接输出到标准错误输出上

3: return value : 最后一个命令的返回值

4: ssh: 在没有重定向时,标准输出和标准错误输出分别输出到标准输出和标准错误输出

AND and OR

cmd1 && cmd2: 只有当cmd1的退出码是 0 时,cmd2才会执行

cmd1 || cmd2  : 只有当cmd1的退出码是 非零 时,cmd2才会执行

AND and OR exit status: 最后一个命令的退出码。

至于Linux的退出码为什么限定在0-255,现在也没找到说明文档。man bash 只有规定而没有说为什么这么规定.如有知道的,谢谢告知


<think>我们遇到了一个链接错误:`undefined reference to ...`,这通常表示链接器在链接阶段找不到某些全局变量的定义。根据之前的讨论,跨文件使用全局变量需要正确定义和声明。以下是可能的原因和解决方案: ### 错误原因分析: 1. **未定义全局变量**: - 错误中提到的`g_client_top_cmdTable`和`get_prompt`在链接时找不到定义,说明这些全局变量或函数在项目中未被正确定义。 2. **声明与定义不匹配**: - 可能使用了`extern`声明,但没有在任何一个源文件中提供实际定义。 3. **编译遗漏**: - 定义全局变量的源文件未被编译或链接到最终可执行文件中。 4. **作用域限制**: - 如果全局变量被定义为`static`,则只能在当前文件内访问。 ### 解决步骤: #### 步骤1:检查全局变量定义 确保在**一个且仅一个**源文件(.c文件)中定义了这些全局变量: ```c // 例如在 global_defs.c 中 CmdTable g_client_top_cmdTable; // 确保类型正确 char* get_prompt() { // 如果get_prompt是函数,需定义 return "my_prompt>"; } ``` #### 步骤2:检查头文件声明 在头文件中使用`extern`声明: ```c // globals.h extern CmdTable g_client_top_cmdTable; // 与定义类型一致 extern char* get_prompt(void); // 函数声明 ``` #### 步骤3:检查文件包含 确保所有使用这些全局变量的源文件包含了声明它们的头文件: ```c // shell_io_util.c #include "globals.h" // 确保包含 ``` #### 步骤4:检查编译链接 确保定义全局变量的源文件被正确编译和链接: - 检查Makefile或编译命令,确认包含定义全局变量的源文件(如`global_defs.c`)被编译并链接到最终目标中。 #### 步骤5:处理常量与函数 - 如果`get_prompt`是函数,确保其定义在一个源文件中,且声明为`extern`(或在头文件中声明)。 - 对于常量全局变量,需使用`extern`声明并在一个源文件中定义(带`const`)。 ### 常见陷阱: 1. **头文件中定义变量**: ```c // 错误示例(在头文件中定义): int g_var = 10; // 若多个.c文件包含此头,链接时重复定义 ``` 正确做法:头文件中只放`extern`声明,定义在.c文件中。 2. **未链接定义文件**: - 如果定义全局变量的.c文件未被编译,或生成的目标文件(.o)未被链接,会导致该错误。 3. **拼写或作用域错误**: - 检查变量名拼写是否一致(包括大小写)。 - 确保没有在定义时误用`static`。 ### 调试建议: 1. 使用`nm`命令检查目标文件: ```bash nm -gC your_object_file.o | grep g_client_top_cmdTable ``` - 查看定义该符号的目标文件是否在链接列表中。 2. 检查链接命令: - 确保所有涉及的目标文件(包括库文件)都出现在链接命令中。 §§相关问题§§ 1. 链接器报`undefined reference`错误时,有哪些系统工具可以帮助定位问题? 2. 在大型项目中如何避免因头文件重复定义导致的链接错误? 3. `static`关键字对全局变量和函数的作用域分别产生什么影响? 4. 为什么在C++中使用全局变量时,有时需要`extern "C"`声明? 5. 如何通过编译选项(如`-fPIC`)解决共享库中的全局变量问题?</think>### 链接错误原因分析与解决方案 您遇到的错误是典型的链接阶段错误:`undefined reference to ...`,表明链接器在目标文件中找不到全局变量的定义。根据您描述的上下文(已按规范修改),以下是可能的原因和解决方案: #### 核心问题诊断: 1. **全局变量未实际定义**: - 在头文件中使用`extern`进行了声明 - 但**未在任何.c文件中提供实际定义** - 错误示例: ```c // globals.h(只有声明) extern CmdTable g_client_top_cmdTable; extern char* get_prompt(void); ``` ```c // 缺少对应的.c文件定义(如global_defs.c) /* 应有但缺失的定义 */ CmdTable g_client_top_cmdTable = {0}; // 未实现 char* get_prompt() { return "> "; } // 未实现 ``` 2. **定义文件未参与编译**: - 包含变量定义的源文件未被添加到构建系统 - 在Makefile/CMake中遗漏了定义文件: ```makefile # 错误示例:缺少定义文件 OBJ_FILES = main.o shell_io_util.o # 缺少global_defs.o ``` 3. **命名空间污染**: - 头文件中存在同名的`static`变量定义 - 导致实际定义被覆盖: ```c // 错误示例(在头文件中定义) static char* get_prompt = "test"; // 会覆盖外部声明 ``` 4. **C/C++混合编译问题**: - 当C++文件使用C定义的全局变量时 - 缺少`extern "C"`包装: ```cpp // 在C++文件中需要 extern "C" { #include "minishell_globals.h" } ``` #### 完整解决方案: 1. **添加实际定义文件**: ```c /* global_defs.c */ #include "minishell_globals.h" // 实际定义全局变量 CmdTable g_client_top_cmdTable = { .commands = NULL, .count = 0 }; // 实际定义函数 char* get_prompt(void) { return g_is_root ? "# " : "$ "; } ``` 2. **确保编译链接定义文件**: ```makefile # 修正后的Makefile示例 OBJS = main.o shell_io_util.o global_defs.o minishell: $(OBJS) gcc -o $@ $^ ``` 3. **检查头文件正确定义**: ```c /* minishell_globals.h */ #ifndef MINISHELL_GLOBALS_H #define MINISHELL_GLOBALS_H typedef struct { /* CmdTable 定义 */ } CmdTable; // 仅声明不定义 extern CmdTable g_client_top_cmdTable; extern char* get_prompt(void); #endif ``` 4. **验证符号存在性**: ```bash # 检查目标文件是否包含符号 nm global_defs.o | grep g_client_top_cmdTable # 应显示:0000000000000000 B g_client_top_cmdTable # 查看链接过程 gcc -v main.o shell_io_util.o global_defs.o -o minishell ``` #### 预防措施: 1. **初始化所有全局变量**(避免BSS段问题) 2. **使用编译警告选项**: ```bash gcc -Wall -Werror -Wextra ... ``` 3. **头文件静态断言**: ```c _Static_assert(sizeof(CmdTable) == 16, "Invalid CmdTable size"); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值