1. 程式人生 > >jenkins執行shell讀不到環境變數問題

jenkins執行shell讀不到環境變數問題

目錄
  • 環境: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讀取不到/etc/profile以及使用者下.bash_profile設定的環境變數。

環境:Red Hat Enterprise 5.5

根據jenkins-core專案Shell.java的buildCommandLine方法

   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,如下

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可知:

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裡可以這麼寫

#!/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