目录
- 环境:Red Hat Enterprise 5.5
- 什么是交互式shell(interactive shell)和非交互式shell(non-interactive shell)
- 什么是登录式shell(login shell)和非登陆式shell(no-login shell)
- 解决方案
- 环境:HP-UX
- 解决方案
从jenkins远程执行shell源码分析系列中,了解到jenkins执行shell的原理。在使用jenkins过程中,发现执行shell读取不到/etc/profile以及用户下.bash_profile设置的环境变量。
环境:Red Hat Enterprise 5.5
根据jenkins-core项目Shell.java的buildCommandLine方法
代码语言:javascript复制 public String[] buildCommandLine(FilePath script) {
if(command.startsWith("#!")) {
// interpreter override
int end = command.indexOf('n');
if(end<0) end=command.length();
List<String> args = new ArrayList<String>();
args.addAll(Arrays.asList(Util.tokenize(command.substring(0,end).trim())));
args.add(script.getRemote());
args.set(0,args.get(0).substring(2)); // trim off "#!"
return args.toArray(new String[args.size()]);
} else
return new String[] { getDescriptor().getShellOrDefault(script.getChannel()), "-xe", script.getRemote()};
}
在默认的情况下,执行shell会在节点上tmp目录生成类似hudson224519953209659762.sh(后面数字根据规则生成),具体执行的命令如:
/bin/sh -xe /tmp/hudson224519953209659762.sh。
如果Execute Shell里面具体命令为以下内容:
#!/bin/bash x
...
...
那么根据上面代码,具体执行的命令就会变成/bin/bash x /tmp/hudson224519953209659762.sh
知道jenkins执行shell的原理后,接下来我们要谈谈交互式和非交互式shell、登录和非登录shell之间的区别
什么是交互式shell(interactive shell)和非交互式shell(non-interactive shell)
交互式的shell会有一个输入提示符,并且它的标准输入、输出和错误输出都会显示在控制台上。这种模式也是大多数用户非常熟悉的:登录、执行一些命令、退出。当你退出后,shell也终止了。
非交互式shell是bash script.sh这类的shell。在这种模式下,shell不与你进行交互,而是读取存放在文件中的命令,并且执行它们。当它读到文件的结尾EOF,shell也就终止了。
什么是登录式shell(login shell)和非登陆式shell(no-login shell)
需要输入用户名和密码的shell就是登陆式shell。因此通常不管以何种方式登陆机器后用户获得的第一个shell就是login shell。不输入密码的ssh是公钥打通的,某种意义上说也是输入密码的。
非登陆式的就是在登陆后启动bash等,即不是远程登陆到主机这种。
通过man bash了解login shell和interactive shell,如下
代码语言:javascript复制INVOCATION
A login shell is one whose first character of argument zero is a -, or one started with the --login option.
An interactive shell is one started without non-option arguments and without the -c option whose standard input
and error are both connected to terminals (as determined by isatty(3)), or one started with the -i option. PS1
is set and $- includes i if bash is interactive, allowing a shell script or a startup file to test this state.
The following paragraphs describe how bash executes its startup files. If any of the files exist but cannot be
read, bash reports an error. Tildes are expanded in file names as described below under Tilde Expansion in the
EXPANSION section.
When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it
first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it
looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from
the first one that exists and is readable. The --noprofile option may be used when the shell is started to
inhibit this behavior.
When a login shell exits, bash reads and executes commands from the file ~/.bash_logout, if it exists.
When an interactive shell that is not a login shell is started, bash reads and executes commands from
~/.bashrc, if that file exists. This may be inhibited by using the --norc option. The --rcfile file option
will force bash to read and execute commands from file instead of ~/.bashrc.
When bash is started non-interactively, to run a shell script, for example, it looks for the variable BASH_ENV
in the environment, expands its value if it appears there, and uses the expanded value as the name of a file to
read and execute. Bash behaves as if the following command were executed:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
but the value of the PATH variable is not used to search for the file name.
对man bash解读:
如果一个bash是交互式登录Shell或者使用--login参数的非交互式Shell。首先会执行/etc/profile文件。然后按顺序查找~/.bash_profile, ~/.bash_login,~/.profile,这三个文件谁存在并且有读权限就执行谁,然后后面的就不会再执行。可以通过指定--noprofile参数来禁止这种默认行为。当登录Shell退出之后,bash会读取~/.bash_logout文件并执行。
如果 ~/.bash_profile文件存在的话,一般还会执行 ~/.bashrc文件。因为在~/.bash_profile文件中一般会有下面的代码:
if -f ~/.bashrc ; then
. ~/.bashrc
fi
~/.bashrc中,一般还会有以下代码:
if -f /etc/bashrc ; then
. /etc/bashrc
fi
所以执行顺序为:
/etc/profile -> (~/.bash_profile | ~/.bash_login | ~/.profile) -> ~/.bashrc -> /etc/bashrc -> ~/.bash_logout
如果是一个交互式非登录Shell,bash会读取~/.bashrc文件。同时,可以指定--norc参数来禁止该行为,或者通过--rcfile指定其它文件。
如果是一个非交互式非登录Shell,比如运行一个Shell脚本,它会在环境查找BASH_ENV变量。
通过上面的分析,对于常用环境变量设置文件,整理出如下加载情况表:
文件 | 非交互 登陆式 | 交互 登陆式 | 交互 非登陆式 | 非交互 非登陆式 | |
---|---|---|---|---|---|
/etc/profile | 加载 | 加载 | | | |
/etc/bashrc | 加载 | 加载 | | | |
~/.bash_profile | 加载 | 加载 | | | |
~/.bashrc | 加载 | 加载 | 加载 | | |
BASH_ENV | | | | 加载 | |
执行脚本,如bash script.sh是属于non-login non-interactive
所以jenkins默认情况下/bin/sh -xe /tmp/hudson224519953209659762.sh 是属于non-login non-interactive
解决方案
通过man bash可知:
代码语言:javascript复制OPTIONS
In addition to the single-character shell options documented in the description of the set builtin command,
bash interprets the following options when it is invoked:
-c string If the -c option is present, then commands are read from string. If there are arguments after the
string, they are assigned to the positional parameters, starting with $0.
-i If the -i option is present, the shell is interactive.
-l Make bash act as if it had been invoked as a login shell (see INVOCATION below).
-r If the -r option is present, the shell becomes restricted (see RESTRICTED SHELL below).
-s If the -s option is present, or if no arguments remain after option processing, then commands are
read from the standard input. This option allows the positional parameters to be set when invoking
an interactive shell.
-D A list of all double-quoted strings preceded by $ is printed on the standard output. These are the
strings that are subject to language translation when the current locale is not C or POSIX. This
implies the -n option; no commands will be executed.
可以通过-i参数和-l参数让bash为login shell and interactive shell,就可以读取/etc/profile和~/.bash_profile等文件。
即在jenkins Execute Shell里可以这么写
#!/bin/bash -ilex
...
...
对于e参数表示一旦出错,就退出当前的shell,x参数表示可以显示所执行的每一条命令
环境:HP-UX
HP-UX中使用的默认shell是POSIX shell,也就是/usr/bin/sh,并且提供了ksh和csh,但就是不提供bash,所以要使用bash需要自己安装。
通过man sh-posix了解到上述的解决方案是不能使用,因为sh-posix有-i参数,但是没有-l参数。
解决方案
所以在HP-UX环境下,在jenkins Execute Shell里可以这么写
代码语言:javascript复制#!/bin/sh x
. /etc/profile
BPFile="$HOME/.bash_profile"
PFile="$HOME/.profile"
exeFile=""
if [ -f "$PFile" ]; then
exeFile=". $PFile"
elif [ -f "$BPFile" ]; then
exeFile=". $BPFile"
fi
$exeFile
set -e
...
...
通过shell执行/etc/profile和~/.profile来模拟login shell and interactive shell