有时我需要维护调用 shell 脚本的程序,而 shell 脚本又调用其他程序和脚本。因此,当主 shell 脚本以退出代码 126 结束时,很难找出哪个调用的脚本和命令设置了该退出代码。
有没有办法查看哪个命令是退出代码的原因,以便更轻松地检查其权限?
答案1
如果在 Linux 上,您可以运行以下命令strace -fe process
来了解哪个进程执行了操作exit_group(126)
以及它(或它的任何父进程,如果它本身没有执行任何内容)在执行此操作之前最后执行了哪些命令:
$ strace -fe process sh -c 'env sh -c /; exit'
execve("/bin/sh", ["sh", "-c", "env sh -c /; exit"], [/* 53 vars */]) = 0
arch_prctl(ARCH_SET_FS, 0x7f24713b1700) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f24713b19d0) = 26325
strace: Process 26325 attached
[pid 26324] wait4(-1, <unfinished ...>
[pid 26325] execve("/usr/bin/env", ["env", "sh", "-c", "/"], [/* 53 vars */]) = 0
[pid 26325] arch_prctl(ARCH_SET_FS, 0x7fbdb4e2c700) = 0
[pid 26325] execve("/bin/sh", ["sh", "-c", "/"], [/* 53 vars */]) = 0
[pid 26325] arch_prctl(ARCH_SET_FS, 0x7fef90b3b700) = 0
[pid 26325] clone(strace: Process 26326 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fef90b3b9d0) = 26326
[pid 26325] wait4(-1, <unfinished ...>
[pid 26326] execve("/", ["/"], [/* 53 vars */]) = -1 EACCES (Permission denied)
sh: 1: /: Permission denied
[pid 26326] exit_group(126) = ?
[pid 26326] +++ exited with 126 +++
[pid 26325] <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 126}], 0, NULL) = 26326
[pid 26325] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26326, si_uid=10031, si_status=126, si_utime=0, si_stime=0} ---
[pid 26325] exit_group(126) = ?
[pid 26325] +++ exited with 126 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 126}], 0, NULL) = 26325
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=26325, si_uid=10031, si_status=126, si_utime=0, si_stime=0} ---
exit_group(126) = ?
+++ exited with 126 +++
上面,进程 26326 首先以 126 退出,这是因为它尝试执行/
.最后执行的是进程 26325 的子进程sh -c /
。
如果这些脚本是bash
脚本或者它们是sh
脚本并且sh
恰好bash
在您的系统上,您可以执行以下操作:
$ env SHELLOPTS=xtrace \
BASH_XTRACEFD=7 7>&2 \
PS4='[$?][$BASHPID|${BASH_SOURCE:-$BASH_EXECUTION_STRING}|$LINENO]+ ' \
sh -c 'env sh -c /; exit'
[0][30625|env sh -c /; exit|0]+ env sh -c /
[0][30626|/|0]+ /
sh: /: Is a directory
[126][30625|env sh -c /; exit|0]+ exit
这并没有告诉我们到底哪个进程以 126 退出,但可以给你足够的线索。
我们使用BASH_TRACEFD=7 7>&2
以便痕迹输出在原来的stderr,即使 stderr 在脚本内重定向也是如此。否则,如果脚本执行类似的操作,这些跟踪消息可能会影响脚本的行为(....) 2>&1 | ...
。假设这些脚本本身没有显式使用或关闭 fd 7(这是不可能的,比它们重定向 stderr 的可能性要大得多)。
答案2
如果设置该选项set -x
,那么您会在跟踪中看到在 shell 中执行的命令。
答案3
这有点像黑客,但您可以预加载一些 C 代码作为填充程序来捕获调用exit(126)
并向进程组发出信号SIGSTOP
,这将暂停进程(及其同一组中的父进程)。
例如,如果我们在 shim 中捕获退出代码 2,然后ls
在不存在的文件上运行:
LD_PRELOAD=/home/meuh/shim_exit.so bash -c ' sh -c "ls -l xxx; echo"; echo '
它将以消息作为背景
[1]+ Stopped ...
您可以看到处于等待状态的进程T
:
~ $ ps f
PID TTY STAT TIME COMMAND
30528 pts/3 T 0:00 \_ bash -c sh -c "ls -l xxx;echo";echo
30529 pts/3 T 0:00 | \_ sh -c ls -l xxx;echo
30530 pts/3 T 0:00 | \_ ls -l xxx
在此阶段,您可以附加到可调试的进程,或者只是将进程置于前台或 SIGCONT 以便继续。
这里是shim_exit.c代码,编译见C注释。
/*
* capture calls to a routine and replace with your code
* http://unix.stackexchange.com/a/308694/119298
* gcc -Wall -O2 -fpic -shared -ldl -o shim_exit.so shim_exit.c
* LD_PRELOAD=/home/meuh/shim_exit.so ./test
*/
#define _GNU_SOURCE /* needed to get RTLD_NEXT defined in dlfcn.h */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <dlfcn.h>
/* doesnt work for syscall exit_group() */
void exit(int status){
static void (*real_exit)(int status) = NULL;
if (!real_exit) {
real_exit = dlsym(RTLD_NEXT, "exit");
char *error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
_exit(1);
}
}
if (status==126/* || status==2*/)kill(0,SIGSTOP);
real_exit(status);
}