這篇文章是我在一個叫做Charlotte數據挖掘的公眾號上看到的文章,文首提到轉載自"朱小廝的博客",當我今天執行一個自己編譯的可執行文件后的運行階段想到了這篇文章,直接一次成功。非常感謝這篇文章。現在記錄下來,僅供學習。
在Linux中,我們執行內置命令時,直接輸入命令名稱即可,如:
$ mv a b #將a重命名為b
而在執行自己寫好的程序時,卻要帶上./,例如:
$ hello
hello: command not found
$ ./hello
hello world
這是為什么呢?它們有什么區別呢?
shell是如何運行程序的
在說明清楚問題之前,我們必須了解shell是如何運行程序的。首先我們必須要清楚的是,執行一條Linux命令,本質是在運行一個程序,如執行ls命令,它執行的是ls程序。那么在shell中輸入一條命令,到底發生了什么?它會經歷哪幾個查找過程?
alias中查找
alias命令可用來設置命令別名,而單獨輸入alias可以查看到已設置的別名:
$ alias
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'
如果這里沒有找到你執行的命令,那么就會接下去查找。如果找到了,那么就會執行下去。
內置命令中查找
不同的shell包含一些不同的內置命令,通常不需要shell到磁盤中去搜索。通過help命令可以看到有哪些內置命令:
$ help
通過type 命令可以查看命令類型:
$ type echo
echo is a shell builtin
如果是內置命令,則會直接執行,否則繼續查找。
PATH中查找
以ls為例,在shell輸入ls時,首先它會從PATH環境變量中查找,PATH內容是什么呢,我們看看:
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
所以它會在這些路徑下去尋找ls程序,按照路徑找到的第一個ls程序就會被執行。使用whereis也能確定ls的位置:
$ whereis ls
ls: /bin/ls /usr/share/man/man1/ls.1.g
既然它是在bin目錄下,那么我把ls從bin目錄下移走是不是就找不到了呢?是的。
$ mv /bin/ls /temp/ls_bak #測試完后記得改回來奧
現在再來執行ls命令看看:
$ ls
The program 'ls' is currently not installed. You can install it by typing:
apt install coreutils
沒錯,它會提示你沒有安裝這個程序或者命令沒有找到。
所以你現在明白為什么你第一次安裝jdk或者python的時候要設置環境變量了吧?不設置的話行不行?
行。這個時候你就需要指定路徑了。怎么指定路徑?無非就是那么幾種,相對路徑,絕對路徑等等。
比如:
$ cd /temp
$ ./ls_bak
或者:
$ /temp/ls_bak
是不是發現和運行自己的普通程序方式沒什么差別呢?
到這里,如果還沒有找到你要執行的命令,那么就會報錯。
確定解釋程序
在找到程序之后呢,需要確定解釋程序。什么意思呢?
shell通常可以執行兩種程序,一種是二進制程序,一種是腳本程序。
而一旦發現要執行的程序文件是文本文件,且文本未指定解釋程序,那么就會默認當成shell腳本來執行。例如,假設有test.txt內容如下:
echo -e "hello world"
賦予執行權限並執行:
$ chmod +x test.txt
$ ./test.txt
hello world
當然了,我們通常會在shell腳本程序的來頭帶上下面這句:
#!/bin/bash
這是告訴shell,你要用bash程序來解釋執行test.txt。作為一位調皮的開發者,如果開頭改成下面這樣呢?
#!/usr/bin/python
再次執行之后結果如下:
$ ./test.txt
File "./test.txt", line 2
echo -e "hello world"
^
SyntaxError: invalid syntax
是的,它被當成python腳本來執行了,自然就會報錯了。
那么如果是二進制程序呢?就會使用execl族函數去創建一個新的進程來運行新的程序了。
小結一下前面的內容,就是說,如果是文本程序,且開頭沒有指定解釋程序,則按照shell腳本處理,如果指定了解釋程序,則使用解釋程序來解釋運行;對於二進制程序,則直接創建新的進程即可。
運行
前面我們也已經看到了運行方式,設置環境變量或者使用相對路徑,絕對路徑即可。不過對於shell腳本,你還可以像下面這樣執行:
$ sh test.txt
$ . test.txt
即便test.txt沒有執行權限,也能夠正常執行。
什么?你說為什么txt也能執行?注意,Linux下的文件后綴不過是為了方便識別文件類型罷了,以.txt結尾,並不代表一定是文本。當然在這里它確實是,而且還是ASCII text executable:
$ file test.txt
test.txt: Bourne-Again shell script, ASCII text executable
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=8ae48f0f84912dec98511581c876aa042824efdb, not stripped
擴展一下
那么如果讓我們自己的程序也能夠像Linux內置命令一樣輸入即可被識別呢?
將程序放到PATH路徑下
第一種方法就是將我們自己的程序放到PATH中的路徑中去,這樣在shell輸入hello時,也能找到,例如我們將其放在/bin目錄下:
$ hello
hello world
$ whereis hello
hello: /bin/hello
也就是說,如果你的程序安裝在了PATH指定的路徑,就需要配置PATH環境變量,在命令行輸入就可以直接找到了。
設置PATH環境變量
那么如果想在指定的目錄能夠直接運行呢?很簡單,那就是添加環境變量,例如將當前路徑加入到PATH中:
$ PATH=$PATH:./ #這種方式只在當前shell有效,所有shell生效可修改/etc/profile文件
$ hello
hello world
設置別名
例如:
$ alias hello="/temp/hello"
$ hello
hello world
以上三種方法都可以達到目的。
執行順序
那么假設我寫了一個自己的printf程序,當執行printf的時候,到底執行的是哪一個呢?
實際上它的查找順序可以可以通過type -a來查看:
$ type -a printf
printf is aliased to `printf "hello\n"'
printf is a shell builtin
printf is /usr/bin/printf
printf is ./printf
這里就可以很清楚地看到查找順序了。也就是說,如果你輸入printf,它執行的是:
$ printf
hello
而如果刪除別名:
unalias printf
它執行的將會是內置命令printf。
以此類推。
總結
說到這里,想必標題的問題以及下面的問題你都清楚了:
-
安裝Python或者Jdk程序為什么要設置PATH環境變量?如果不設置,該如何運行?
-
除了./方式運行自己的程序還有什么方式?
-
如果讓自己的程序能夠像內置命令一樣被識別?
-
如何查看文件類型?
-
執行一條命令,如何確定是哪里的命令被執行
本文涉及命令:
-
mv 移動/重命名
-
file 查看文件信息
-
whereis 查看命令或者手冊位置
-
type 查看命令類別