站長資訊網
        最全最豐富的資訊網站

        Linux下set,env,export,source,exec深入解析

        你是否被下面的幾個問題困擾過,甚至至今無法真正理解?

        什么是export,什么時候用export,為什么有時用了export還要source? 為什么用env來設置環境變量,不用export,有什么好處? source和exec有什么區別?

        本文試圖通過普及unix進程、環境變量等概念,讓讀者真真理解這些shell命令的本質,知道這些命令的使用場合。

        clipboard.png

        首先,先對這些命令做一個解釋,如果讀者能完全理解,那么本文也許對你幫助不大。

        set設置了當前shell進程的本地變量,本地變量只在當前shell的進程內有效,不會被子進程繼承和傳遞。 env僅為將要執行的子進程設置環境變量。 export將一個shell本地變量提升為當前shell進程的環境變量,從而被子進程自動繼承,但是export的變量無法改變父進程的環境變量。 source運行腳本的時候,不會啟用一個新的shell進程,而是在當前shell進程環境中運行腳本。 exec運行腳本或命令的時候,不會啟用一個新的shell進程,并且exec后續的腳本內容不會得到執行,即當前shell進程結束了。

        在這些表述中,反復提到進程和環境變量的概念。如果希望深入理解其中的含義,還必須理解進程的相關概念。

        進程和環境變量

        進程是一個程序執行的上下文集合,這個集合包括程序代碼、數據段、堆棧、環境變量、內核標識進程的數據結構等。一個進程可以生成另一個進程,生成的進程稱為子進程,那么相應的就有父進程,所謂子子孫孫無窮盡也。子進程從父進程處會繼承一些遺傳因素,其中就包括本文的主題環境變量。環境變量是一組特殊的字符型變量,由于具有繼承性質,環境變量也經常用于父子進程傳遞參數用,這一點在shell編程中尤為突出。

        fork和exec

        在unix系統中進程通過依次調用fork()和exec()系統調用來實現創建一個子進程。

        fork其實就是克隆,為什么github復刻別人的項目叫fork?就是這么來的,所謂“克隆”,就是在內存中將當前進程的所有內存鏡像復制一份,所有東西都一樣,只修改新進程的進程號(PID)。有點類似細胞分裂,細胞分裂后生成的細胞具有與原細胞完全相同的遺傳因素。因為fork()會復制整個進程,包括進程運行到哪句代碼,這意味著新的進程會繼續執行fork()后面的代碼,父進程也會運行fork()后面的代碼,從fork()開始父子進程才分道揚鑣。如果fork返回>0,那么說明在父進程中,如果fork返回==0,說明在子進程中:

        pid = fork();

        if(pid == 0) {

        //子進程中

        } else if(pid > 0) {

        //父進程

        }

        精確的說exec是一組函數的統稱,并且exec的準確定義是,用磁盤上的一個新的程序替換當前的進程的正文段、數據段、堆棧段。所以exec并不產生新的進程,而是替換。如此一來進程將從新代碼的main開始執行,相當于另外運行了一個完全不同的程序,但保留了原來環境變量。

        依據本文的主題,可以把exec函數分為兩類,一類是可以設置并傳遞新環境變量的,一類是不能傳遞新環境變量的,只能繼承原環境變量的。換句話說,在運行新的程序時,是有機會改變新程序的環境變量的,而不只是繼承。如下面這個變種,可以通過envp參數設置環境變量

        intexecve(constchar* filename,char*constargv[ ],char*constenvp[ ]);

        作為父進程而言,可以通過waitpid()函數等待子進程退出,并獲得退出狀態。

        clipboard.png

        進程可通過setenv或putenv更改自己的環境變量,但環境變量的繼承只能單向,即從父進程繼承給fork出來的子進程。子進程即使修改了自己的環境變量也無法動搖到父進程的環境變量。

        shell

        shell并沒有什么特殊,也是一個進程,當我們在命令行中敲入一個命令,并且按下Enter后,shell這個進程會通過fork和exec為我們創建一個子進程(存在一小部分命令不需要啟動子進程,稱為build-in命令),并且等待(waitpid)這個子進程完成退出。那么進程的內存鏡像顯然就包含本文的主題環境變量。比如,如果我們在shell命令行中執行ls -al,shell實際執行如下偽代碼:

        pid = fork();

        if(pid == 0) {

        //子進程中,調用exec

        exec(“ls -al”);

        } else if(pid > 0) {

        //父進程中,waitpid等待子進程退出

        waitpid(pid);

        }

        上面討論了shell執行命令的情況,如果在命令行中執行一個shell腳本呢?默認情況下,shell進程會創建一個sub-shell子進程來執行這個shell腳本,并且等待這個子進程執行結束。

        最后,再來審視一下本文的主題。首先set,source,export都是shell的build-in命令,命令本身不會創建新進程。

        set其實跟進程創建無關,也跟環境變量無關,它只是當前shell進程內部維護的變量(本地變量),用于變量的引用和展開,不能遺傳和繼承。

        但shell的export命令可以通過調用putenv將一個本地變量提升為當前shell的環境變量。但是,記住環境變量的繼承只是單向的,sub-shell中export的變量在父shell中是看不到的。有什么辦法可以讓一個腳本中的export印象到父進程的環境變量呢?

        答案是使用source執行腳本,source的用法如下:

        source ./test.sh

        如果用source執行腳本,意味著fork和exec不會被調用,當前shell直接對test.sh解釋執行。這樣的話,如果此時test.sh中有export(即putenv),那么將會改變當前shell的環境變量。

        export如此好用,但是問題是它幾乎會影響到其后的所有命令,有沒有辦法可以在運行某個命令時,臨時啟用某個環境變量,而不影響后面的命令呢?

        答案是使用env,env的用法如下:

        env GOTRACEBACK=crash ./test.sh

        env不是shell的build-in命令,所以shell執行env的時候還是需要創建子進程的

        env的作用從本質上說,相當于shell先fork,然后在子進程中運行env,子進程env調用execve運行test.sh時,多傳了一個GOTRACEBACK=crash的環境變量(上文提到過execve是可以改變默認的繼承行為的),這樣test.sh可以看到這個GOTRACEBACK環境變量,但由于沒有調用putenv改變父shell的環境變量,所以后續啟動的進程并不繼承GOTRACEBACK。

        exec意味著不調用fork,而是直接調用exec執行!這意味著當前shell的代碼執行到exec后,代碼被替換成了exec要執行的程序,自然地,后續的shell腳本不會得到執行,因為shell本身都被替換掉了。

        clipboard.png

        上圖的env實際并不準確,因為env不是build-in命令,讀者可自行腦補

        嗯,光是從理論去理解,或許沒那么好消化,不如動手“實作+思考”來的印象深刻哦。

        問題一:寫兩個簡單的script,分別命名為1.sh及2.sh:

        1.sh

        #!/bin/bash

        A=B

        echo “PID for 1.sh before exec/source/fork:$$”

        export A

        echo “1.sh: $A is $A”

        case $1 in

        exec)

        echo “using exec…”

        exec ./2.sh;;

        source)

        echo “using source…”

        ../2.sh;;

        *)

        echo “using fork by default…”

        ./2.sh;;

        esac

        echo “PID for 1.sh after exec/source/fork:$$”

        echo “1.sh: $A is $A”

        2.sh

        #!/bin/bash

        echo “PID for 2.sh: $$”

        echo “2.sh get $A=$A from 1.sh”

        A=C

        export A

        echo “2.sh: $A is $A”

        然后,分別跑如下參數來觀察結果:

        $ ./1.sh fork

        $ ./1.sh source

        $ ./1.sh exec

        問題二:用env設置環境變量后,運行的腳本中又調用了其他腳本,這個環境變量還會繼承下去嗎?

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 国产精品第一区第27页| 国产精品无码A∨精品影院| 在线精品国产一区二区三区| 国产亚洲欧美精品久久久| 国产精品v欧美精品v日本精| 亚洲av日韩精品久久久久久a| www.精品| 国产精品欧美久久久久天天影视| 无码日韩精品一区二区三区免费| 国产成人毛片亚洲精品| 久久久精品人妻一区二区三区蜜桃 | 国产精品欧美亚洲韩国日本久久 | 国产精品白浆在线观看免费| 久久精品国产清自在天天线| 国产2021精品视频免费播放| 亚洲一日韩欧美中文字幕欧美日韩在线精品一区二 | 国产精品一二三区| 亚洲AV成人精品网站在线播放| 精品久久久久久国产牛牛app| 欧洲精品99毛片免费高清观看| 四虎成人www国产精品| 精品久久久无码人妻中文字幕豆芽| 久久久久国产精品麻豆AR影院| 久久精品国产99国产精品澳门| 久久久精品人妻一区二区三区蜜桃| 久久夜色精品国产亚洲av| 国产精品毛片无码| 日韩精品久久久久久久电影蜜臀| 精品国产一区二区22| 国产亚洲精品影视在线产品| 国产精品人人做人人爽人人添| 9re热国产这里只有精品| 6080亚洲精品午夜福利| 久久这里只有精品首页| 午夜精品福利视频| 国产精品成人国产乱一区| 国产91在线精品| 精品亚洲欧美无人区乱码| 久久夜色精品国产| 最新欧美性爱精品一区二区三区 | 欧美日韩精品在线|