ssh鏈接遠程主機執行腳本的環境變量問題

用ssh命令ssh user@remote "/web/tomcat-7000/bin/startup.sh" 登錄到遠程機器remote上執行腳本時,遇到一個奇怪的問題:tomcat服務不能啓動html

Neither the JAVA_HOME nor the JRE_HOME environment variable is defined
At least one of these environment variable is needed to run this programweb

 

問題分析&處理:shell

      一、將命令單獨分開apache

          a、ssh遠程登陸服務器   ssh user@remote編程

          b、手動執行腳本  /web/tomcat-7000/bin/startup.sh緩存

          c、執行成功tomcat

      二、爲tomcat啓動腳本配置環境變量bash

  在 /web/tomcat-7000/bin/startup.sh上配置添加      服務器

 

  添加tomcat環境變量app

  export MAVEN_HOME=/usr/local/apache-maven-3.3.3
  export JAVA_HOME=/usr/local/jdk
  export CROMOLOG_HOME=/usr/local/cronolog-1.6.2/
  export PATH=$CROMOLOG_HOME/sbin:$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH
  export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

     三、從新退出服務器,執行腳本,避免緩存 Ok

 

 

請參考如下ssh的模式

如下轉發: http://www.kuqin.com/shuoit/20141113/343188.html

近日在使用ssh命令ssh user@remote ~/myscript.sh登錄到遠程機器remote上執行腳本時,遇到一個奇怪的問題:

~/myscript.sh: line n: app: command not found

app是一個新安裝的程序,安裝路徑明明已經過/etc/profile配置文件加到環境變量中,但這裏爲什麼會找不到?若是直接登錄機器remote並執行~/myscript.sh時,app程序能夠找到並順利執行。但爲何使用了ssh遠程執行一樣的腳本就出錯了呢?兩種方式執行腳本到底有何不一樣?若是你也心存疑問,請跟隨我一塊兒來展開分析。

目錄


說明,本文所使用的機器是:SUSE Linux Enterprise。

問題定位

這看起來像是環境變量引發的問題,爲了證明這一猜測,我在這條命令以前加了一句:which app,來查看app的安裝路徑。在remote本機上執行腳本時,它會打印出app正確的安裝路徑。但再次用ssh來執行時,卻遇到下面的錯誤:

which: no app in (/usr/bin:/bin:/usr/sbin:/sbin)

這很奇怪,怎麼括號中的環境變量沒有了app程序的安裝路徑?不是已經過/etc/profile設置到PATH中了?再次在腳本中加入echo $PATH並以ssh執行,這才發現,環境變量還是系統初始化時的結果:

/usr/bin:/bin:/usr/sbin:/sbin

這證實/etc/profile根本沒有被調用。爲何?是ssh命令的問題麼?

隨後我又嘗試了將上面的ssh分解成下面兩步:

user@local > ssh user@remote    # 先遠程登錄到remote上 user@remote> ~/myscript.sh      # 而後在返回的shell中執行腳本

結果居然成功了。那麼ssh以這兩種方式執行的命令有何不一樣?帶着這個問題去查詢了man ssh

If command is specified, it is executed on the remote host instead of a login shell.

這說明在指定命令的狀況下,命令會在遠程主機上執行,返回結果後退出。而未指定時,ssh會直接返回一個登錄的shell。但到這裏仍是沒法理解,直接在遠程主機上執行和在返回的登錄shell中執行有什麼區別?即便在遠程主機上執行不也是經過shell來執行的麼?難道是這兩種方式使用的shell有什麼不一樣?

暫時尚未頭緒,但隱隱感到應該與shell有關。由於我一般使用的是bash,因此又去查詢了man bash,才獲得了答案。

bash的四種模式

在man page的INVOCATION一節講述了bash的四種模式,bash會依據這四種模式而選擇加載不一樣的配置文件,並且加載的順序也有所不一樣。本文ssh問題的答案就存在於這幾種模式當中,因此在咱們揭開謎底以前先來分析這些模式。

interactive + login shell

第一種模式是交互式的登錄shell,這裏面有兩個概念須要解釋:interactive和login:

login故名思義,即登錄,login shell是指用戶以非圖形化界面或者以ssh登錄到機器上時得到的第一個shell,簡單些說就是須要輸入用戶名和密碼的shell。所以一般無論以何種方式登錄機器後用戶得到的第一個shell就是login shell。

interactive意爲交互式,這也很好理解,interactive shell會有一個輸入提示符,而且它的標準輸入、輸出和錯誤輸出都會顯示在控制檯上。因此通常來講只要是須要用戶交互的,即一個命令一個命令的輸入的shell都是interactive shell。而若是無需用戶交互,它即是non-interactive shell。一般來講如bash script.sh此類執行腳本的命令就會啓動一個non-interactive shell,它不須要與用戶進行交互,執行完後它便會退出建立的shell。

那麼此模式最簡單的兩個例子爲:

  • 用戶直接登錄到機器得到的第一個shell
  • 用戶使用ssh user@remote得到的shell

加載配置文件

這種模式下,shell首先加載/etc/profile,而後再嘗試依次去加載下列三個配置文件之一,一旦找到其中一個便再也不接着尋找:

  • ~/.bash_profile
  • ~/.bash_login
  • ~/.profile

下面給出這個加載過程的僞代碼:

execute /etc/profile IF ~/.bash_profile exists THEN     execute ~/.bash_profile ELSE     IF ~/.bash_login exist THEN         execute ~/.bash_login     ELSE         IF ~/.profile exist THEN             execute ~/.profile         END IF     END IF END IF

爲了驗證這個過程,咱們來作一些測試。首先設計每一個配置文件的內容以下:

 

1 user@remote > cat /etc/profile 2 echo @ /etc/profile 3 user@remote > cat ~/.bash_profile 4 echo @ ~/.bash_profile 5 user@remote > cat ~/.bash_login 6 echo @ ~/.bash_login 7 user@remote > cat ~/.profile 8 echo @ ~/.profile

 

而後打開一個login shell,注意,爲方便起見,這裏使用bash -l命令,它會打開一個login shell,在man bash中能夠看到此參數的解釋:

-l Make bash act as if it had been invoked as a login shell

進入這個新的login shell,便會獲得如下輸出:

@ /etc/profile @ /home/user/.bash_profile

果真與文檔一致,bash首先會加載全局的配置文件/etc/profile,而後去查找~/.bash_profile,由於其已經存在,因此剩下的兩個文件再也不會被查找。

接下來移除~/.bash_profile,啓動login shell獲得結果以下:

@ /etc/profile @ /home/user/.bash_login

由於沒有了~/.bash_profile的屏蔽,因此~/.bash_login被加載,但最後一個~/.profile仍被忽略。

再次移除~/.bash_login,啓動login shell的輸出結果爲:

@ /etc/profile @ /home/user/.profile

~/.profile終於熬出頭,得見天日。經過以上三個實驗,配置文件的加載過程獲得了驗證,除去/etc/profile首先被加載外,其他三個文件的加載順序爲:~/.bash_profile>~/.bash_login>~/.profile,只要找到一個便終止查找。

前面說過,使用ssh也會獲得一個login shell,因此若是在另一臺機器上運行ssh user@remote時,也會獲得上面同樣的結論。

配置文件的意義

那麼,爲何bash要弄得這麼複雜?每一個配置文件存在的意義是什麼?

/etc/profile很好理解,它是一個全局的配置文件。後面三個位於用戶主目錄中的配置文件都針對用戶我的,也許你會問爲何要有這麼多,只用一個~/.profile很差麼?究竟每一個文件有什麼意義呢?這是個好問題。

Cameron Newham和Bill Rosenblatt在他們的著做《Learning the bash Shell, 2nd Edition》的59頁解釋了緣由:

bash allows two synonyms for .bash_profile: .bash_login, derived from the C shell’s file named .login, and .profile, derived from the Bourne shell and Korn shell files named .profile. Only one of these three is read when you log in. If .bash_profile doesn’t exist in your home directory, then bash will look for .bash_login. If that doesn’t exist it will look for .profile.

One advantage of bash’s ability to look for either synonym is that you can retain your .profile if you have been using the Bourne shell. If you need to add bash-specific commands, you can put them in .bash_profile followed by the command source .profile. When you log in, all the bash-specific commands will be executed and bash will source .profile, executing the remaining commands. If you decide to switch to using the Bourne shell you don’t have to modify your existing files. A similar approach was intended for .bash_login and the C shell .login, but due to differences in the basic syntax of the shells, this is not a good idea.

原來一切都是爲了兼容,這麼設計是爲了更好的應付在不一樣shell之間切換的場景。由於bash徹底兼容Bourne shell,因此.bash_profile.profile能夠很好的處理bash和Bourne shell之間的切換。可是因爲C shell和bash之間的基本語法存在着差別,做者認爲引入.bash_login並非個好主意。因此由此咱們能夠得出這樣的最佳實踐:

  • 應該儘可能杜絕使用.bash_login,若是已經建立,那麼須要建立.bash_profile來屏蔽它被調用
  • .bash_profile適合放置bash的專屬命令,能夠在其最後讀取.profile,如此一來,即可以很好的在Bourne shell和bash之間切換了

non-interactive + login shell

第二種模式的shell爲non-interactive login shell,即非交互式的登錄shell,這種是不太常見的狀況。一種建立此shell的方法爲:bash -l script.sh,前面提到過-l參數是將shell做爲一個login shell啓動,而執行腳本又使它爲non-interactive shell。

對於這種類型的shell,配置文件的加載與第一種徹底同樣,在此再也不贅述。

interactive + non-login shell

第三種模式爲交互式的非登錄shell,這種模式最多見的狀況爲在一個已有shell中運行bash,此時會打開一個交互式的shell,而由於再也不須要登錄,所以不是login shell。

加載配置文件

對於此種狀況,啓動shell時會去查找並加載/etc/bash.bashrc~/.bashrc文件。

爲了進行驗證,與第一種模式同樣,設計各配置文件內容以下:

 

1 user@remote > cat /etc/bash.bashrc 2 echo @ /etc/bash.bashrc 3 user@remote > cat ~/.bashrc 4 echo @ ~/.bashrc

 

而後咱們啓動一個交互式的非登錄shell,直接運行bash便可,能夠獲得如下結果:

@ /etc/bash.bashrc @ /home/user/.bashrc

由此很是容易的驗證告終論。

bashrc VS profile

從剛引入的兩個配置文件的存放路徑能夠很容易的判斷,第一個文件是全局性的,第二個文件屬於當前用戶。在前面的模式當中,已經出現了幾種配置文件,多數是以profile命名的,那麼爲何這裏又增長兩個文件呢?這樣不會增長複雜度麼?咱們來看看此處的文件和前面模式中的文件的區別。

首先看第一種模式中的profile類型文件,它是某個用戶惟一的用來設置全局環境變量的地方, 由於用戶能夠有多個shell好比bash, sh, zsh等, 但像環境變量這種其實只須要在統一的一個地方初始化就能夠, 而這個地方就是profile,因此啓動一個login shell會加載此文件,後面由此shell中啓動的新shell進程如bash,sh,zsh等均可以由login shell中繼承環境變量等配置。

接下來看bashrc,其後綴rc的意思爲Run Commands,由名字能夠推斷出,此處存放bash須要運行的命令,但注意,這些命令通常只用於交互式的shell,一般在這裏會設置交互所須要的全部信息,好比bash的補全、alias、顏色、提示符等等。

因此能夠看出,引入多種配置文件徹底是爲了更好的管理配置,每一個文件各司其職,只作好本身的事情。

non-interactive + non-login shell

最後一種模式爲非交互非登錄的shell,建立這種shell典型有兩種方式:

  • bash script.sh
  • ssh user@remote command

這兩種都是建立一個shell,執行完腳本以後便退出,再也不須要與用戶交互。

加載配置文件

對於這種模式而言,它會去尋找環境變量BASH_ENV,將變量的值做爲文件名進行查找,若是找到便加載它。

一樣,咱們對其進行驗證。首先,測試該環境變量未定義時配置文件的加載狀況,這裏須要一個測試腳本:

 

1 user@remote > cat ~/script.sh 2 echo Hello World

 

而後運行bash script.sh,將獲得如下結果:

Hello World

從輸出結果能夠得知,這個新啓動的bash進程並無加載前面提到的任何配置文件。接下來設置環境變量BASH_ENV

 

1 user@remote > export BASH_ENV=~/.bashrc

 

再次執行bash script.sh,結果爲:

@ /home/user/.bashrc Hello World

果真,~/.bashrc被加載,而它是由環境變量BASH_ENV設定的。

更爲直觀的示圖

至此,四種模式下配置文件如何加載已經講完,由於涉及的配置文件有些多,咱們再以兩個圖來更爲直觀的進行描述:

第一張圖來自這篇文章,bash的每種模式會讀取其所在列的內容,首先執行A,而後是B,C。而B1,B2和B3表示只會執行第一個存在的文件:

+----------------+--------+-----------+---------------+ |                | login  |interactive|non-interactive| |                |        |non-login  |non-login      | +----------------+--------+-----------+---------------+ |/etc/profile    |   A    |           |               | +----------------+--------+-----------+---------------+ |/etc/bash.bashrc|        |    A      |               | +----------------+--------+-----------+---------------+ |~/.bashrc       |        |    B      |               | +----------------+--------+-----------+---------------+ |~/.bash_profile |   B1   |           |               | +----------------+--------+-----------+---------------+ |~/.bash_login   |   B2   |           |               | +----------------+--------+-----------+---------------+ |~/.profile      |   B3   |           |               | +----------------+--------+-----------+---------------+ |BASH_ENV        |        |           |       A       | +----------------+--------+-----------+---------------+

上圖只給出了三種模式,緣由是第一種login實際上已經包含了兩種,由於這兩種模式下對配置文件的加載是一致的。

另一篇文章給出了一個更直觀的圖:

Bash加載文件順序

上圖的狀況稍稍複雜一些,由於它使用了幾個關於配置文件的參數:--login--rcfile--noprofile--norc,這些參數的引入會使配置文件的加載稍稍發生改變,不過整體來講,不影響咱們前面的討論,相信這張圖不會給你帶來更多的疑惑。

典型模式總結

爲了更好的理清這幾種模式,下面咱們對一些典型的啓動方式各屬於什麼模式進行一個總結:

  • 登錄機器後的第一個shell:login + interactive
  • 新啓動一個shell進程,如運行bash:non-login + interactive
  • 執行腳本,如bash script.sh:non-login + non-interactive
  • 運行頭部有如#!/usr/bin/env bash的可執行文件,如./executable:non-login + non-interactive
  • 經過ssh登錄到遠程主機:login + interactive
  • 遠程執行腳本,如ssh user@remote script.sh:non-login + non-interactive
  • 遠程執行腳本,同時請求控制檯,如ssh user@remote -t 'echo $PWD':non-login + interactive
  • 在圖形化界面中打開terminal:
  • Linux上: non-login + interactive
  • Mac OS X上: login + interactive

相信你在理解了login和interactive的含義以後,應該會很容易對上面的啓動方式進行歸類。

再次嘗試

在介紹完bash的這些模式以後,咱們再回頭來看文章開頭的問題。ssh user@remote ~/myscript.sh屬於哪種模式?相信此時你能夠很是輕鬆的回答出來:non-login + non-interactive。對於這種模式,bash會選擇加載$BASH_ENV的值所對應的文件,因此爲了讓它加載/etc/profile,能夠設定:

 

1 user@local > export BASH_ENV=/etc/profile

 

而後執行上面的命令,可是很遺憾,發現錯誤依舊存在。這是怎麼回事?彆着急,這並非咱們前面的介紹出錯了。仔細查看以後才發現腳本myscript.sh的第一行爲#!/usr/bin/env sh,注意看,它和前面提到的#!/usr/bin/env bash不同,可能就是這裏出了問題。咱們先嚐試把它改爲#!/usr/bin/env bash,再次執行,錯誤果真消失了,這與咱們前面的分析結果一致。

第一行的這個語句有什麼用?設置成sh和bash有什麼區別?帶着這些疑問,再來查看man bash

If the program is a file beginning with #!, the remainder of the first line specifies an interpreter for the program.

它表示這個文件的解釋器,即用什麼程序來打開此文件,就比如Windows上雙擊一個文件時會以什麼程序打開同樣。由於這裏不是bash,而是sh,那麼咱們前面討論的都不復有效了,真糟糕。咱們來看看這個sh的路徑:

 

1 user@remote > ll `which sh` 2 lrwxrwxrwx 1 root root 9 Apr 25  2014 /usr/bin/sh -> /bin/bash

 

原來sh只是bash的一個軟連接,既然如此,BASH_ENV應該是有效的啊,爲什麼此處無效?仍是回到man bash,一樣在INVOCATION一節的下部看到了這樣的說明:

If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well. When invoked as an interactive login shell, or a non-interactive shell with the –login option, it first attempts to read and execute commands from /etc/profile and ~/.profile, in that order. The –noprofile option may be used to inhibit this behavior. When invoked as an interactive shell with the name sh, bash looks for the variable ENV, expands its value if it is defined, and uses the expanded value as the name of a file to read and execute. Since a shell invoked as sh does not attempt to read and execute commands from any other startup files, the –rcfile option has no effect. A non-interactive shell invoked with the name sh does not attempt to read any other startup files. When invoked as sh, bash enters posix mode after the startup files are read.

簡而言之,當bash以是sh命啓動時,即咱們此處的狀況,bash會盡量的模仿sh,因此配置文件的加載變成了下面這樣:

  • interactive + login: 讀取/etc/profile~/.profile
  • non-interactive + login: 同上
  • interactive + non-login: 讀取ENV環境變量對應的文件
  • non-interactive + non-login: 不讀取任何文件

這樣即可以解釋爲何出錯了,由於這裏屬於non-interactive + non-login,因此bash不會讀取任何文件,故而即便設置了BASH_ENV也不會起做用。因此爲了解決問題,只須要把sh換成bash,再設置環境變量BASH_ENV便可。

另外,其實咱們還能夠設置參數到第一行的解釋器中,如#!/bin/bash --login,如此一來,bash便會強制爲login shell,因此/etc/profile也會被加載。相比上面那種方法,這種更爲簡單。

配置文件建議

回顧一下前面提到的全部配置文件,總共有如下幾種:

  • /etc/profile
  • ~/.bash_profile
  • ~/.bash_login
  • ~/.profile
  • /etc/bash.bashrc
  • ~/.bashrc
  • $BASH_ENV
  • $ENV

不知你是否會有疑問,這麼多的配置文件,究竟每一個文件裏面應該包含哪些配置,好比PATH應該在哪?提示符應該在哪配置?啓動的程序應該在哪?等等。因此在文章的最後,我搜羅了一些最佳實踐供各位參考。(這裏只討論屬於用戶我的的配置文件)

 
  • ~/.bash_profile:應該儘量的簡單,一般會在最後加載.profile.bashrc(注意順序)
  • ~/.bash_login:在前面討論過,別用它
  • ~/.profile:此文件用於login shell,全部你想在整個用戶會話期間都有效的內容都應該放置於此,好比啓動進程,環境變量等
  • ~/.bashrc:只放置與bash有關的命令,全部與交互有關的命令都應該出如今此,好比bash的補全、alias、顏色、提示符等等。特別注意:別在這裏輸出任何內容(咱們前面只是爲了演示,別學我哈)

寫在結尾

至此,咱們詳細的討論完了bash的幾種工做模式,而且給出了配置文件內容的建議。經過這些模式的介紹,本文開始遇到的問題也很容易的獲得瞭解決。之前雖然一直使用bash,但真的不清楚裏面包含了如此多的內容。同時感覺到Linux的文檔的確作得很是細緻,在徹底不須要其它安裝包的狀況下,你就能夠獲得一個很是完善的開發環境,這也曾是Eric S. Raymond在其著做《UNIX編程藝術》中提到的:UNIX天生是一個很是完善的開發機器。本文幾乎全部的內容你均可以經過閱讀man page獲得。最後,但願在這樣一個被妖魔化的特殊日子裏,這篇文章可以爲你帶去一絲幫助。

 

轉發: http://www.kuqin.com/shuoit/20141113/343188.html

相關文章
相關標籤/搜索