Linux環境變數問題彙總

plantegg發表於2018-04-24

原文連結

摘要: ### 測試好的指令碼放到 crontab 裡就報錯: 找不到命令 寫好一個指令碼,測試沒有問題,然後放到crontab 想要定時執行,但是總是報錯,去看日誌的話顯示某些命令找不到,這種一般都是因為PATH環境變數變了導致的 自己在shell命令列下測試的時候當前環境變數就是這個使用者的環境變...

測試好的指令碼放到 crontab 裡就報錯: 找不到命令

寫好一個指令碼,測試沒有問題,然後放到crontab 想要定時執行,但是總是報錯,去看日誌的話顯示某些命令找不到,這種一般都是因為PATH環境變數變了導致的

自己在shell命令列下測試的時候當前環境變數就是這個使用者的環境變數,可以通過命令:env 看到,指令碼放到crontab 裡面後一般都加了sudo 這個時候 env 變了。比如你可以在命令列下執行 env 和 sudo env 比較一下就發現他們很不一樣

為了解決這個問題 sudo有一個引數 -E (--preserver-env)就是為了解決這個問題的。

這個時候再比較一下

  • env
  • sudo env
  • sudo -E env

大概就能理解這裡的區別了

同樣一個命令ssh執行不了, 報找不到命令

比如:

ssh user@ip " ip a " 報錯: bash: ip: command not found

但是你要是先執行 ssh user@ip 連上伺服器後,再執行 ip a 就可以,這裡是同一個命令通過兩種不同的方式但是環境變數也不一樣了。

同樣想要解決這個問題的話可以先 ssh 連上伺服器,再執行 which ip ; env | grep PATH

$ which ip
/usr/sbin/ip
$ env | grep PATH
PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin

很明顯這裡因為 ip在/usr/sbin下,而/usr/sbin又在PATH變數中,所以可以找到。

那麼接下來我們看看

$ssh user@ip "env | grep PATH"
PATH=/usr/local/bin:/usr/bin

很明顯這裡的PATH比上面的PATH短了一截,/usr/sbin也沒有在裡面,所以/usr/sbin 下的ip命令自然也找不到了,這裡雖然都是同一個使用者,但是他們的環境變數還不一樣,有點出乎我的意料之外。

主要原因是我們的shell 分為login shell 和 no-login shell , 先ssh 登陸上去再執行命令就是一個login shell,Linux要為這個終端分配資源。

而下面的直接在ssh 裡面放執行命令實際上就不需要login,所以這是一個no-login shell.

login shell 和 no-login shell又有什麼區別呢?

  • login shell載入環境變數的順序是:① /etc/profile ② ~/.bash_profile ③ ~/.bashrc ④ /etc/bashrc
  • 而non-login shell載入環境變數的順序是: ① ~/.bashrc ② /etc/bashrc

也就是nog-login少了前面兩步,我們先看後面兩步。

下面是一個 .bashrc 的內容:

[ ~]$ cat .bashrc 
# .bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
    . /etc/bashrc
fi

基本沒有什麼內容,它主要是去載入 /etc/bashrc 而他裡面也沒有看到sbin相關的東西

那我們再看non-login少的兩步: ① /etc/profile ② ~/.bash_profile

cat /etc/profile :


if [ "$EUID" = "0" ]; then
    pathmunge /usr/sbin
    pathmunge /usr/local/sbin
else
    pathmunge /usr/local/sbin after
    pathmunge /usr/sbin after
fi

這幾行程式碼就是把 /usr/sbin 新增到 PATH 變數中,正是他們的區別決定了這裡的環境變數不一樣。

用一張圖來表述他們的結構,箭頭代表載入順序,紅框代表不同的shell的初始入口
image.png

像 ansible 這種自動化工具,或者我們自己寫的自動化指令碼,底層通過ssh這種non-login的方式來執行的話,那麼都有可能碰到這個問題,如何修復呢?

在 /etc/profile.d/ 下建立一個檔案:/etc/profile.d/my_bashenv.sh 內容如下:


$cat /etc/profile.d/my_bashenv.sh 

pathmunge () {
if ! echo $PATH | /bin/egrep -q "(^|:)$1($|:)" ; then
   if [ "$2" = "after" ] ; then
  PATH=$PATH:$1
   else
  PATH=$1:$PATH
   fi
fi
}
 
pathmunge /sbin
pathmunge /usr/sbin
pathmunge /usr/local/sbin
pathmunge /usr/local/bin
pathmunge /usr/X11R6/bin after
 
unset pathmunge

complete -cf sudo
 

alias chgrp='chgrp --preserve-root'
alias chown='chown --preserve-root'
alias chmod='chmod --preserve-root'
alias rm='rm -i --preserve-root'
 

HISTTIMEFORMAT='[%F %T] '
HISTSIZE=1000

export EDITOR=vim


export PS1='\n\e[1;37m[\e[m\e[1;32m\u\e[m\e[1;33m@\e[m\e[1;35m\H\e[m \e[4m`pwd`\e[m\e[1;37m]\e[m\e[1;36m\e[m\n\$'

通過前面我們可以看到 /etc/bashrc 總是會去載入 /etc/profile.d/ 下的所有 *.sh 檔案,同時我們還可以在這個檔案中修改我們喜歡的 shell 配色方案和環境變數等等

BASH

1、互動式的登入shell (bash –il xxx.sh)
載入的資訊:
/etc/profile
~/.bash_profile( -> ~/.bashrc -> /etc/bashrc)
~/.bash_login
~/.profile

2、非互動式的登入shell (bash –l xxx.sh)
載入的資訊:
/etc/profile
~/.bash_profile ( -> ~/.bashrc -> /etc/bashrc)
~/.bash_login
~/.profile
$BASH_ENV

3、互動式的非登入shell (bash –i xxx.sh)
載入的資訊:
~/.bashrc ( -> /etc/bashrc)

4、非互動式的非登入shell (bash xxx.sh)
載入的資訊:
$BASH_ENV

SH

1、互動式的登入shell
載入的資訊:
/etc/profile
~/.profile

2、非互動式的登入shell
載入的資訊:
/etc/profile
~/.profile

3、互動式的非登入shell
載入的資訊:
$ENV

練習驗證一下bash、sh和login、non-login

  • sudo ll 或者 sudo cd 是不是都報找不到命令
  • 先sudo bash 然後執行 ll或者cd就可以了
  • 先sudo sh 然後執行 ll或者cd還是報找不到命令
  • sudo env | grep PATH 然後 sudo bash 後再執行 env | grep PATH 看到的PATH環境變數不一樣了

4、非互動式的非登入shell
載入的資訊:
nothing

export命令的作用

Linux 中export是一種命令工具通過export命令把shell變數中包含的使用者變數匯入給子程式.預設情況下子程式僅會繼承父程式的環境變數,子程式不會繼承父程式的自定義變數,所以需要export讓父程式中的自定義變數變成環境變數,然後子程式就能繼承過來了。

我們來看一個例子, 有一個變數,名字 abc 內容123 如果沒有export ,那麼通過bash建立一個新的shell(新shell是之前bash的子程式),在新的shell裡面就沒有abc這個變數, export之後在新的 shell 裡面才可以看到這個變數,但是退出重新login後(產生了一個新的bash,只會載入env)abc變數都不在了

$echo $abc


$abc="123"


$echo $abc
123

$bash

$echo $abc


$exit
exit

$export abc

$echo $abc
123

$bash

$echo $abc
123

一些常見問題

執行好好地shell 指令碼換臺伺服器就:source: not found

source 是bash的一個內建命令(所以你找不到一個/bin/source 這樣的可執行檔案),也就是他是bash自帶的,如果我們執行指令碼是這樣: sh shell.sh 而shell.sh中用到了source命令的話就會報 source: not found

這是因為bash 和 sh是兩個東西,sh是 POSIX shell,你可以把它看成是一個相容某個規範的shell,而bash是 Bourne-Again shell script, bash是 POSIX shell的擴充套件,就是bash支援所有符合POSIX shell的規範,但是反過來就不一定了,而這裡的 source 恰好就是 bash內建的,不符合 POSIX shell的規範(POSIX shell 中用 . 來代替source)

在centos執行好好的指令碼放到Ubuntu上就不行了,報語法錯誤

同上,如果到ubuntu上用 bash shell.sh是可以的,但是sh shell.sh就報語法錯誤,但是在centos上執行:sh或者bash shell.sh 都可以通過。 在centos上執行 ls -lh /usr/bin/sh 可以看到 /usr/bin/sh link到了 /usr/bin/bash 也就是sh等同於bash,所以都可以通過不足為奇。

但是在ubuntu上執行 ls -lh /usr/bin/sh 可以看到 /usr/bin/sh link到了 /usr/bin/dash , 這就是為什麼ubuntu上會報錯

source shell.sh 和 bash shell.sh以及 ./shell.sh的區別

source shell.sh就在本shell中展開執行
bash shell.sh表示在本shell啟動一個子程式(bash),在子程式中執行 shell.sh (shell.sh中產生的一些環境變數就沒法帶回父shell程式了), 有讀 shell.sh 許可權就可以執行
./shell.sh 跟bash shell.sh類似,但是必須要求shell.sh有rx許可權,然後根據shell.sh前面的 #! 後面的指示來確定用bash還是sh

$cat test.sh 
echo 

 

$echo$echo

2299

$source test.sh 
2299

$bash test.sh 
4037

$./test.sh 
4040

如上例項,只有source的時候程式ID和bash程式ID一樣,其它方式都建立了一個新的bash程式,所以ID也變了

參考文章:

關於ansible遠端執行的環境變數問題

Bash和Sh的區別

什麼是互動式登入 Shell what-is-interactive-and-login-shell

Shell 預設選項 himBH 的解釋

useful-documents-about-shell

相關文章