1. 程式人生 > >Linux的PS1.PS2.PS3.PS4等環境變量;Crontab的兩個坑人點;變量傳遞等

Linux的PS1.PS2.PS3.PS4等環境變量;Crontab的兩個坑人點;變量傳遞等

返回 也會 bashrc 有效 特性 example com cal命令 無法

1.問題出現:

我為了實現一個功能,就是讓PS1變量(命令行提示符)每隔1分鐘(利用crontab計劃任務)變化一次顏色和背景格式以實現酷炫的效果,但是經過了各種嘗試均以失敗告終。雖然能夠實現讓PS1每按一次回車變化一次顏色(這個有人想嘗試的話下面寫的有),但是無法做到讓它每隔一段時間進行一次格式的變化
為了解決這個問題,進行了一些研究,總結了一下寫在下面

附加:PS1每按一次回車實現顏色變化實現:

  1. 先在腳本中寫入:

    #!/bin/bash
    PS1="\033[01;\$[RANDOM%7+31]m\A[\[email protected]\h \w]\\$\033[0m " 
  2. 然後命令行直接source 此腳本即可(或者把它放到/etc/profile.d文件夾下,每次開機開啟shell後就會自動執行)

註意點:

  • 這裏當然也可以直接在命令行中輸入腳本中所寫的那個命令,不過這樣的話關閉shell後下次就會丟失
  • PS1的值不能用RANDOM計算過之後的值,不然它只能在source的時候執行一次並取了一次RANDOM的值,之後就固定了,而這裏所寫的PS1利用了\$[RANDOM]把它給註釋掉了,因此此時查看PS1可知:
11:01[[email protected] /data/scriptest]# echo $PS1
\033[01;$[RANDOM%7+31]m\A[\[email protected]\h \w]\$\033[0m
  • 因此PS1每按一次回車就會重新執行上面所寫的變量賦值(顯示出PS1命令提示符),所以可以實現每次按回車變換顏色的功能。
  • 由此也可以得出在linux中命令提示符的值是每按一次回車就會根據PS1變量中所寫的內容進行輸出並顯示在屏幕上的,而並非讀入內存之後就一成不變了

2.以下是問題的分析和總結:

環境變量

  1. 我們知道,環境變量(全局變量)雖然能夠在當前shell以及它的子shell中使用,但在子shell中僅僅是調用它而已,雖然能繼承並使用這個變量的值,但是這個子shell並不能改變它所調用的環境變量的值並傳遞給父shell中。註意這和函數不同,看下面的例子:
20:59[[email protected] /data/scriptest]# declare -x aaa=12345
20:59[[email protected] /data/scriptest]# echo $aaa
12345
20:59[[email protected] /data/scriptest]# ./testsource2.sh
12345
123123
20:59[[email protected] /data/scriptest]# echo $aaa
12345
  1. 而定義一個函數則它便是在當前shell中運行的,並未開啟子shell,因此若不用local命令定義局部變量,則環境變量會被函數給改變。
    • 這裏在命令行中定義函數的時候註意中括號中每個命令後面都要加上分號,且前面的中括號要和命令之間有空格,後面的沒要求。

沒定義local

21:01[[email protected] /data/scriptest]# echo $aaa
12345
21:01[[email protected] /data/scriptest]# funsor() { aaa=555 ; return 0 ; } 
21:02[[email protected] /data/scriptest]# echo $aaa
12345
21:02[[email protected] /data/scriptest]# funsor
21:02[[email protected] /data/scriptest]# echo $aaa
555

定義local

21:08[[email protected] /data/scriptest]# echo $aaa
12345
21:08[[email protected] /data/scriptest]# funsor2() { local aaa=555 ; echo $aaa ; return 0 ; } 
21:08[[email protected] /data/scriptest]# funsor2
555
21:08[[email protected] /data/scriptest]# echo $aaa
12345

特別註意點(目前測試過的,下面有測試過程A和B):

  1. 自己在shell開啟後的命令行中或者說在開啟shell的時候載入的配置文件中定義的環境變量,只要不是下面中所說的極特殊的那些(不能被直接繼承的),都能夠在當前shell中開啟的子shell中被繼承。
  2. 系統(shell自動配置的)中常用的環境變量,在linux的bash shell中,如果再次開啟子bash shell,按照分析得知應該能全部被繼承(因為環境變量的特性),但測試得知並非完全如此:

而能夠被這個子shell直接繼承的有(基本上在開機後shell開啟後用declare -x命令查看到的這些出現的變量都能夠繼承)

  1. PATH變量
  2. PATH
  3. PWD
  4. HOSTNAME
  5. HISTSIZE
  6. HISTCONTROL
  7. PS3:
    • 它是命令select後選擇時出現的選擇提示符,默認是沒有定義的空字符,且默認不是環境變量,此時會顯示#?。
    • 經過測試把它定義為環境變量並賦值之後,在子shell中能夠直接繼承父shell的PS3

不能夠被直接繼承的有

  1. PS1:

    • 它就是命令行的提示符的值,可以有很多格式,具體查看幫助man bash.
    • 它默認不是一個環境變量,但是就算用命令declare -x把它定義為了環境變量,子shell也無法繼承,子shell中經過測試為空;
    • 由此可見這個PS1應該是一個特殊變量,這也側面解釋了bash shell開啟的時候默認沒把它定義為環境變量,下面幾個PS同理
  2. PS2:

    • 同上,默認不是環境變量,定義為環境變量之後也無法繼承;
    • 它是一個非常長的命令可以通過在末尾加“\”使其分行顯示後的多行命令的默認提示符,默認已經定義為普通變量,值為"> "
  3. PS4:

    • 同上
    • 它就是set -x命令用來修改跟蹤輸出的前綴,默認定義為普通變量且值為"+ "
  4. 更多的其他還沒測試,以後補充,不過從1中可見有些特殊變量就算定義為了全局變量,在子shell開啟的時候也會把它覆蓋掉從而無法繼承(相當於在shell開啟過程是中重新聲明定義了這些變量,這個就是開啟shell時的內部邏輯了)
    • 註意,以上說的將PS定義為環境變量都是開啟shell時之後的操作,只是存入內存中了,如果另開新的shell,這些操作都會失效,恢復到默認的shell設置
    • 同時我們可以想到,只要能找到定義PS1,PS2,PS3,PS4的配置文件位置(shell開啟時),並將它修改為自己想要的值,(就比如上面的PS1每次都改變的命令),這樣每次開啟一個新的shell,就算這些環境變量不能繼承,但是按照shell開啟時載入的配置文件中寫的這些特殊的環境變量默認設置,就能部分實現自己想要的設置。不過更方便的方法還是直接在子shell中source一個配置文件即可,這個配置文件中寫上這些環境變量的賦值即可,唯一的缺憾就是不能修改後傳遞給父shell(這部分不理解先把下面的分析看完再回頭看)。

測試過程A:PS的繼承變量的測試(可先把下面的分析看完再回頭看):

  1. 首先新開一個終端(也就是新開shell)測試
    文件(腳本)中所寫:
    PStest
#!/bin/bash   
echo PS1=$PS1
echo PS2=$PS2
echo PS3=$PS3
echo PS4=$PS4
select i in test1 test2 test3; do                                                                                                                     
        case $i in
        *)
           echo $i
           break
           ;;
        esac
done
  1. 在當前shell中source這個文件結果:
    可以看到默認的PS變量和select提示符:
12:10[[email protected] /data/scriptest]# . PStest 
PS1=\[\033[01;35m\]\A[\[email protected]\h \w]\$\[\033[00m\]
PS2=>
PS3=
PS4=+
1) test1
2) test2
3) test3
#? 2
test2
12:10[[email protected] /data/scriptest]# 
  1. 在當前shell中把PS變量都定義為環境變量:
12:15[[email protected] /data/scriptest]# declare -x PS1 PS2 PS3 PS4
12:15[[email protected] /data/scriptest]# declare -x :   查看
declare -x PS1="\\[\\033[01;35m\\]\\A[\\[email protected]\\h \\w]\\\$\\[\\033[00m\\] "
declare -x PS2="> "
declare -x PS3
declare -x PS4="+ "
  1. 先在子shell中直接測試,也就是直接執行此文件以腳本方式:
    可見就算定義為環境變量,PS1和PS2也沒有繼承,子shell中為空。目前還無法判斷PS3和PS4
12:15[[email protected] /data/scriptest]# PStest 
PS1=
PS2=
PS3=
PS4=+
1) test1
2) test2
3) test3
#? 1
test1
12:17[[email protected] /data/scriptest]# 
  1. 然後在當前shell中修改PS3,PS4的值(上一步已經知道PS1,PS2無法繼承了):
12:21[[email protected] /data/scriptest]# PS3="Please input"
12:22[[email protected] /data/scriptest]# PS4="=== "
12:22[[email protected] /data/scriptest]# declare -x
declare -x PS1="\\[\\033[01;35m\\]\\A[\\[email protected]\\h \\w]\\\$\\[\\033[00m\\] "
declare -x PS2="> "
declare -x PS3="Please input"
declare -x PS4="=== "
  1. 最後再次以腳本方式也就是子shell方式執行此文件(腳本):
    可見PS3可以直接繼承,並且在select中生效了,而PS4並沒有繼承,還是原先的值。
12:22[[email protected] /data/scriptest]# PS1test.sh 
12:24[[email protected] /data/scriptest]# PStest 
PS1=
PS2=
PS3=Please input
PS4=+
1) test1
2) test2
3) test3
Please input3
test3

測試過程B:下面是測試可以被子shell直接繼承的由shell本身默認定義的變量的一些測試過程:

先寫腳本,然後以子shell方式進行測試:

21:36[[email protected] /data/scriptest]# cat testsource.sh -n
     1  #!/bin/bash
     2  echo PATH=$PATH
     3  echo PWD=$PWD
     4  echo HOSTNAME=$HOSTNAME
     5  echo HISTSIZE=$HISTSIZE
     6  echo HISTCONTROL=$HISTCONTROL
     7  
21:36[[email protected] /data/scriptest]# ./testsource.sh 
PATH=/data/app/httpdnew/bin:/data/app/cmatrix/bin:/data/app/tree/bin:/data/scriptest/:.:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/bin:/root/bin
PWD=/data/scriptest
HOSTNAME=centos7.6test
HISTSIZE=1000
HISTCONTROL=ignoreboth

crontab的兩個坑人註意點:%和環境變量

crontab的執行過程

它的執行過程比較特殊,它執行的時候並不會從當前shell中繼承各種系統定義的環境變量和自己定義的環境變量(全局變量)等等,因此必須在它執行的時候傳遞給它各種環境變量才能保證後的命令完全正確的執行。分情況分析:

  1. 系統定義的環境變量等等,這些環境變量在開機(開啟shell)的時候會載入配置文件從而在當前shell中得到數值,而這些環境變量在crontab中基本上都不會繼承。
  2. 自己定義的一些普通變量,比如說自己在/etc/profile.d文件夾中或者/etc/profile, ~/.bashrc等這些配置文件中定義的普通變量,再開機(開啟shell後)並已經載入內存中,這些通通不會繼承,包括直接在命令中定義的普通變量(這些在bash shell中開啟子shell都不會繼承,更別說在crontab中了)
  3. 自己定義的一些環境變量,包括2中說的這些文件中的,或者在命令行中直接定義的環境變量,也不能夠在crontab中繼承
  • 註意,自己在命令行中定義的變量直接就存入了內存中,下次開啟shell就會丟失,而文件中的下次開啟shell不會丟失。但需要區分環境變量和普通變量。

從上面可見crontab幾乎不會繼承任何變量,不論是系統定義的還是自己定義的,不論是環境還是普通變量,不論是內存中的還是文件中的。
它也是開啟了一個子shell,不過與bash shell的區別就在於環境變量不會繼承。因此為了命令的正確進行,可有下面的比較推薦的兩種解決方式:

  1. 直接在crontab -e中的執行頻率後面,真正要執行的命令前面,寫入引用命令: source /etc/profile && source ~/.bash_profile (這裏沒有寫上全部的環境變量配置文件)
    • 這個命令就是為了把這些配置文件(包括自己定義的環境變量文件)引用進crontab執行環境中去,也就是把這些環境變量先導入,然後再執行需要執行的命令
  2. 如果crontab中的命令是要執行腳本,則在後面需要執行的腳本中添加寫入1中的source引用,然後就可以使用這些環境變量了

需要註意點:

  1. crontab 無論如何操作都引用不了自己在當前shell的命令行中直接用命令declare -x(或export)定義的環境變量,因為它們在內存中,無法引用出來。(註意和shell的區別,當前shell中開啟的子腳本(子shell)中可以引用它們,前面已經分析過了)
  2. crontab 在書寫命令的時候最好要加上全局路徑,因為PATH這個變量默認也是沒有引用的,不過PATH這個變量其實默認在/etc/crontab中定義過,crontab是按照這裏面的定義來判斷PATH變量的值的,而不是從當前的shell中繼承。
    • 其實這個文件是可以定義一些環境變量的,比如把PATH等等寫進去和當前shell中的PATH相同,這樣的話crontab執行命令的時候就不用在寫全路徑了
    • 可參照下面原本的格式(上面定義環境變量的部分)來寫,提前定義一些環境變量。不過推薦還是按照上面的兩種方式來解決,因為如果環境變量變化了每次都要修改這個文件:
21:37[[email protected] /data/scriptest]# cat /etc/crontab 
//就是按照下面這3行的格式來定義自己需要的環境變量
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed

source的簡單說明

source的命令其實很簡單,就相當於是在當前的shell中執行文件中的命令(把文件中的每一行命令拉到命令行來執行),類似於函數,因此它能夠改變當前shell的環境變量等等。
這也是為何我們用source來進行配置文件(尤其是環境變量)的修改之後讓它生效的,而不是用 “bash 腳本” 或者添加PATH和執行權限後直接執行腳本的方式來修改環境變量。
因為後兩種方式修改的環境變量只能在子腳本(shell)中有效,而前面說過雖然子腳本能繼承環境變量(除了那些特殊的比如PS1,就算父shell修改PS1定義為環境變量,當開啟子shell後它在子shell中也默認為空值沒有定義),但是修改這些環境變量的值並不能返回到父shell中,也就實現不了使配置文件生效的目的了(其實生效了,不過是在子shell中生效的,子shell一旦退出所有配置便消失不能傳給父shell)

3.問題的結論:

從上面分析得知,不論怎樣都無法在子shell中修改環境變量(包括PS1)的值並傳給父shell,而crontab默認開啟子shell,因此它不僅改不了PS1,其他的環境變量也無法應用到父shell中,就算用source命令也只是在crontab開啟的子shell中應用這些環境變量,不能修改它們傳遞到父shell也就是當前shell中。

  • 同時,我們可知在使用crontab的過程中,不能寫入修改當前shell中任何變量(普通,環境)的命令,就就算寫了,這些命令也都是無效的無法傳回當前shell從而修改這些變量的值。

更多分析:

  • 只有一種方法,也就是用crontab修改文件的內容(文件裏可以寫入環境變量),因為文件是保存在磁盤中的,每次使用它的時候才會讀入內存,這就和shell無關了,也就相當於所有的shell都可以查看並使用這些文件,實現了曲線傳遞數據的方法。
  • 然後退出之後在父shell也就是當前shell中執行一下source命令這些文件,這樣才能夠實現在當前shell中實現環境變量的更新。不過這樣做還得手動source一下,相當於無法自動配置了,crontab也就沒有意義。
  • 不過用這種方式用來配置系統服務和一些其他程序的配置文件,還是能夠生效的(配置完之後別忘了重新讀入配置文件或者重啟),當然也能進行一些備份操作等等(因為備份就是存入文件到磁盤中,和shell無關)
  • 原因就是因為(這些程序如果配置的時候需要環境變量,就按照上面的解決方法來導入環境變量)在crontab修改它們的配置文件後重新載入或者重啟,退出crontab之後這些服務並不會關閉,而此時配置文件已經讀入內存,所以也就實現了shell之間的服務程序的配置傳遞,(如果有必要還可以再加上nohup命令讓它和終端也無關)

crontab的%的說明

這個在crontab中代表換行,想要使用它要麽\%轉義的方式,要麽就把它寫入腳本中,或者寫在單引號中不需要轉義,不過此時就不能用於計算取余或者字符串變量操作中的一些命令了。
但是註意別忘了%它不能在crontab中直接使用

  • 更多關於crontab的使用可看計劃任務博客一章,比較常用的一些:
    tail /var/log/cron :查看cron執行日誌
    cat /var/spool/cron/用戶名:類似於crontab -l ,查看用戶名的用戶定義的crontab命令。

Linux的PS1.PS2.PS3.PS4等環境變量;Crontab的兩個坑人點;變量傳遞等