主題 1 The Shell 課程概覽與 shell · the missing semester of your cs education (missing-semester-cn.github.io) Shell是什麼? 一旦你想脫離可視化界面讓你做的,然後做點別的事情,那麼Shell將是你和計 ...
主題 1 The Shell
課程概覽與 shell · the missing semester of your cs education (missing-semester-cn.github.io)
Shell是什麼?
一旦你想脫離可視化界面讓你做的,然後做點別的事情,那麼Shell將是你和電腦交互的最主要的方式之一。
可視化界面受限於,它只能做被設計出來的操作——比如你不能點擊一個不存在的按鈕或者是用語音輸入一個還沒有被錄入的指令。這就是這門課介紹命令行工具和基於文本的工具的理由,shell則是你去做這些操作的地方。
在Windows和Linux可以找到成堆的終端(Terminal),這些是能顯示Shell的文本視窗。其中普遍的是bash,或者叫Bourne Again Shell。由於bash的普遍性,這門課中將使用bash。
使用Shell
終端(Terminal)是你電腦上和shell交互的主要文本界面。
當你打開一個終端,你通常會在終端中看到這樣的一行,稱為命令行提示符(Shell Prompt)
[root@VM-8-17-centos ~]#
它告訴你,你的主機名是VM-8-17-centos
,你的用戶名是root
,還有你當前所在的路徑為~
(path)。
可以在終端上執行命令,通常是帶著參數(argument)執行程式。參數一般是一些緊隨程式名後面的,用空格分開的東西。
- date
date輸入當前日期和時間
[root@VM-8-17-centos ~]# date
Sat Dec 17 01:04:35 CST 2022
- echo
echo列印出你傳給它的參數
[root@VM-8-17-centos ~]# echo hello
hello
- 參數以空格分隔
如上所說,參數是被空格分隔的,如果傳遞一個多單詞的參數,就必須用引號括起來,如:
[root@VM-8-17-centos ~]# echo "Hello Wrold"
Hello Wrold
這樣echo程式會收到一個字元串參數Hello World
,中間還有一個空格。此外使用單引號也是可以的。
單雙引號的區別將在bash scripting 再說
此外也可以使用轉義符將空格轉義,如:
[root@VM-8-17-centos ~]# echo Hello\ World
Hello World
關於如何給參數,變數轉義,解析和加括弧將在之後涉及
我們在創建目錄或文件時,如果某個參數是帶空格的,也需要使用引號轉義或者用轉義符將空格轉義,否者shell將會將該參數識別成兩個參數。
如下shell將my photo
識別成兩個參數,創建了兩個目錄:
[root@VM-8-17-centos ~]# mkdir my photo
[root@VM-8-17-centos ~]# ls
my photo
正確的做法為:
[root@VM-8-17-centos ~]# mkdir "my photo"
[root@VM-8-17-centos ~]# ll
total 4
drwxr-xr-x 2 root root 4096 Dec 17 01:23 'my photo'
在Shell中導航
- 環境變數
你可能會好奇,當輸入date或者echo等命令時,Shell怎麼知道這些程式要做什麼。
你的機器可能內嵌了終端程式,或者某些瀏覽器。同樣的,電腦也內嵌了很多圍繞終端工作的程式,這些程式位於你的文件系統(File System),Shell有辦法在系統中搜索某個程式,然後執行。
當然,Shell不會在所有文件中進行搜索,那樣效率太低了。
Shell藉助一個叫做 環境變數(Environment Variable) 的東西來完成搜索。
環境變數就類似編程語言中的變數,Shell或者說bash本身就是一種程式設計語言。你輸入的提示符(Prompt)不僅能帶參運行程式,你也可以寫入while迴圈,for迴圈,條件語句等,甚至可以定義函數,甚至變數。關於Shell Scripting的下一講會有涉及
環境變數是Shell本就設定好的,無論何時打開shell都無需重新設置。一堆東西都會預先設置好,比如哪裡是home目錄,你的用戶名是什麼等。
- PATH變數
如下,當我們執行echo $PATH
時,將會輸出一些電腦上的目錄,這些目錄就是Shell尋找程式時所查找的目錄。這些目錄以冒號分隔。
[root@VM-8-17-centos ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
當你輸入一個程式名稱時,電腦會在這個列表中的每個目錄里,查找名字與你所輸入的指令相同的一個程式或者文件。如果在這些目錄中可以找到待運行的程式,程式可以正常運行,否則失敗。
- which
如果我們想要知道電腦具體運行了哪一個目錄里的程式,可以使用which指令。
[root@VM-8-17-centos ~]# which echo
/usr/bin/echo
[root@VM-8-17-centos ~]# which date
/usr/bin/date
- 路徑
路徑是用來描述你的電腦里文件位置的東西。
在Linux或者Mac Os上,路徑被一連串的斜杠分隔,可以看到上面echo指令的路徑起點在根目錄/
(/ 即整個文件系統的最頂層)
在Windows里,路徑以反斜杠\ 而非斜杠/分隔。
在Linux或Mac Os上,所有東西都在一個叫根(root)的空間的下麵的某處。因此所有以斜杠開頭/的路徑都是絕對路徑。
而在Windows下,每一個分區都有一個根,類似於C:\或者D:\,所以Windows里每一個驅動器(硬碟)下都有獨立的一套文件系統的層次結構。
絕對路徑:是可以絕對準確地確定一個文件的位置的路徑
相對路徑:是相對於你當前所在位置的路徑
- pwd
列印工作目錄(print working directory)
[root@VM-8-17-centos ~]# pwd
/root
你可以改變當前工作目錄,所有的相對路徑都是相對於當前工作目錄的
- cd
change directory 改變當前工作目錄
[root@VM-8-17-centos ~]# cd /home
[root@VM-8-17-centos home]# pwd
/home
shell提示只會給路徑的最後一段名稱,當然也可以通過設置是它總能顯示當前的完整路徑
- 特殊的目錄
.
和..
.
表示當前目錄,..
表示上一級(父)目錄
[root@VM-8-17-centos lighthouse]# pwd
/home/lighthouse
[root@VM-8-17-centos lighthouse]# cd ../../..
[root@VM-8-17-centos /]# pwd
/
使用相對or絕對路徑取決於哪個方便,但是如果有時候你需要運行某個程式或者寫一個程式,它調用了類似echo或者date這樣的程式,你希望它在哪個地方都能跑起來,要麼你就只給出這個要被運行的程式的名字(讓shell用path去找出它們在哪裡),要麼就需要給出絕對路徑
一般來說程式預設在當前目錄運行
- ls
輸入本級目錄下的所有文件信息
[root@VM-8-17-centos /]# ls
bin boot data dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
如果給定路徑參數,則會輸出給定路徑目錄下的文件信息
[root@VM-8-17-centos /]# ls home/lighthouse/
dirdemo hello2.txt hello.txt
- 特殊符號
-
和~
~
表示當前用戶的home目錄
[root@VM-8-17-centos /]# cd ~
[root@VM-8-17-centos ~]# pwd
/root
在cd命令中,-
參數表示之前所處的工作目錄
[root@VM-8-17-centos /]# cd -
/home
[root@VM-8-17-centos home]# cd -
/
- --help
大多數命令都有一個 --help選項,可以幫助你瞭解命令的用法
[root@VM-8-17-centos /]# ls --help
Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
Mandatory arguments to long options are mandatory for short options too.
-a, --all do not ignore entries starting with .
-A, --almost-all do not list implied . and ..
--author with -l, print the author of each file
-b, --escape print C-style escapes for nongraphic characters
--block-size=SIZE with -l, scale sizes by SIZE when printing them;
e.g., '--block-size=M'; see SIZE format below
-B, --ignore-backups do not list implied entries ending with ~
……
……
比如usage這行信息,[ ]表示這部分內容可填可不填,...表示可以填寫多個option或flag
option是有多個參數字元可以選擇,flag是只有一個參數字元選擇
- 許可權
使用ls -l 命令可以以長列表格式輸出當前目錄下的文件信息
[root@VM-8-17-centos lighthouse]# ls -l
total 12
drwxrwxr-x 2 lighthouse lighthouse 4096 Dec 13 00:33 dirdemo
-rw-r--r-- 1 root root 6 Dec 15 19:56 hello2.txt
-rw-rw-r-- 1 lighthouse lighthouse 52 Dec 15 19:59 hello.txt
首先前面帶著d的這行條目,代表這是一個目錄,例如上面的dirdemo就是一個目錄,hello2.txt和hello.txt則是文件。
d後面的字元rwxrwxr-x
代表文件被授予的許可權。
閱讀這一串字元的方法如下:9個字元,每三個一組,分為三組。
第一組:代表許可權被授予給了文件的所有者
第二組:代表給擁有這些文件的用戶組的許可權
第三組:代表給非所有者的其他人的許可權
其中 -
表示該用戶不具備相應的許可權。
同時可以發現所有字元都是有rwx組成的,r(read)表示讀取許可權,w(write)表示寫入許可權,x(execute)表示執行許可權。這三者的許可權使用數字表示:4表示r,2表示w,1表示x。
許可權對於文件和目錄有不同的解釋:
對文件而言,如果你有讀取許可權,可以讀取文件的內容。文件的寫許可權就是
目錄的讀取許可權可以允許你看這個文件夾中有哪些東西(列出這個目錄的內容);目錄的寫入許可權是你能否重命名、新建或者刪除裡面的文件;註意如果你有目錄里的文件的寫入許可權,卻沒有目錄的寫入許可權,那你就不能刪除這個文件(即使你清空了這個文件也不能刪除它,因為這要目錄的寫入許可權);最後是目錄的執行許可權,通常來講就是搜索許可權。這意味著你能不能進入這個目錄。
為了進入某個文件夾,用戶需要具備該文件夾以及其父文件夾的“搜索”許可權(即目錄的執行許可權)
- mv
它接受兩個路徑作為參數,第一個是原有的路徑,第二個是新的路徑。這意味著mv既可以讓你重命名一個文件,又可以讓你移動一個文件。
- cp
copy複製,該命令可以讓你複製文件,用法很類似。它也接受兩個路徑作為參數,複製源路徑和目標路徑,這些路徑要是完整路徑(意為著你需要明確指定文件路徑,這個命令沒有搜索功能)
[root@VM-8-17-centos lighthouse]# cp hello.txt ../food.txt
[root@VM-8-17-centos lighthouse]# cd ..
[root@VM-8-17-centos home]# ls
food.txt lighthouse
- rm
移除(刪除一個文件),你可以傳遞一個路徑作為參數。
需要註意預設的移除是非遞歸的,也就是說你不能rm移除一個目錄(因為目錄中可能會有文件),你可以傳遞一個執行遞歸移除的-r 標識,它就會遞歸刪除目錄下的所有內容
- rmdir
移除目錄,同樣也是非遞歸的,這意味著你不能使用該命令刪除一個非空目錄
- mkdir
創建一個新目錄
- man
manual pages(手冊/說明書),這個程式接受其他程式的名字作為一個參數,然後顯示它的說明書。
和程式名 --help
命令相似。
- 快捷鍵Ctrl+L
清空終端,讓游標回到頂部(和clear命令相似)
在程式間創建連接
- 流(Stream)
程式有兩個主要的流(stream),預設下程式會有一個輸入流(input stream)和一個輸出流(output stream)
-
預設輸入流里的內容來自你的鍵盤,基本上輸入流是終端,無論你向終端輸入什麼,最後都會傳到程式里。
-
預設的輸出流(即當程式想要輸出一些內容時),預設也是終端
這也是為什麼當你在終端中打入
echo hello
時,hello會直接顯示在你的終端里
Shell提供了重定向這些流的方法,把輸入和輸出都改到程式員指明的地方。最直接的方式就是使用大於小於號(即所謂的尖角括弧)。
- 小於號表示重定向這個程式的輸入流
- 大於號表示重定向程式的輸出流
例如:
[root@VM-8-17-centos lighthouse]# echo hello > hello.txt
[root@VM-8-17-centos lighthouse]# cat hello.txt
hello
將echo程式輸出的內容hello,輸入到hello.txt文件中
cat的作用是列印出一個文件的內容,cat同樣支持流的重定向。
在這個例子中,Shell就會打開hello.txt,取出它的內容,設置成cat的輸入,cat就會把這些內容列印到它的輸出流,這裡沒有重定向,所以cat的輸出流還是終端
[root@VM-8-17-centos lighthouse]# cat < hello.txt
hello
也可以同時使用兩種重定向,如
[root@VM-8-17-centos lighthouse]# cat < hello.txt > hello2.txt
[root@VM-8-17-centos lighthouse]# cat hello2.txt
hello
用hello.txt的內容作為cat的輸入流,然後把cat輸出的所欲內容存到hello2.txt中
- 雙大於號
作用是追加(append)而不是覆寫(overwrite)
追加指向文件尾繼續添加內容,覆寫是清空文件再寫入內容
[root@VM-8-17-centos lighthouse]# cat < hello.txt > hello2.txt
[root@VM-8-17-centos lighthouse]# cat hello2.txt
hello
[root@VM-8-17-centos lighthouse]# cat < hello.txt >> hello2.txt
[root@VM-8-17-centos lighthouse]# cat hello2.txt
hello
hello
- 管道符
pipe,管道符就是一個豎線|
。管道的意思是,取左側程式的輸出,稱為右側程式的輸入。
[root@VM-8-17-centos /]# ls -l | tail -n3
drwxrwxrwt. 10 root root 4096 Dec 22 22:03 tmp
drwxr-xr-x. 12 root root 4096 Dec 31 2021 usr
drwxr-xr-x. 20 root root 4096 Dec 31 2021 var
ls的輸出作為tail的輸入,tail的輸出則會輸到終端(因為你沒有重定向tail的輸出)
tail 列印它輸入的最後n行
當然也可以重定向tail的輸出
[root@VM-8-17-centos /]# ls -l | tail -n3 > ls.txt
[root@VM-8-17-centos /]# cat ls.txt
drwxrwxrwt. 10 root root 4096 Dec 22 22:03 tmp
drwxr-xr-x. 12 root root 4096 Dec 31 2021 usr
drwxr-xr-x. 20 root root 4096 Dec 31 2021 var
使用管道可以構建一些複雜的命令:
我們可以做一些操作例如
[root@VM-8-17-centos /]# curl --head --silent baidu.com
HTTP/1.1 200 OK
Date: Thu, 22 Dec 2022 16:17:30 GMT
Server: Apache
Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
ETag: "51-47cf7e6ee8400"
Accept-Ranges: bytes
Content-Length: 81
Cache-Control: max-age=86400
Expires: Fri, 23 Dec 2022 16:17:30 GMT
Connection: Keep-Alive
Content-Type: text/html
這會給你訪問baidu.com的時候所有的HTTP Headers
你可以使用管道將這些輸出接到grep -i content-length
[root@VM-8-17-centos /]# curl --head --silent baidu.com | grep -i content-length
Content-Length: 81
grep 命令支持在輸入流里搜索給定的關鍵字
[root@VM-8-17-centos /]# curl --head --silent baidu.com | grep -i content-length | cut --delimiter=' ' -f2
81
cut 命令可以接收一個分隔符delimiter,將輸入流以分隔符的形式輸出,-f設置輸出第幾個欄位
可以發現通過將命令鏈接起來,你可以做很多文本操作的特技
並且pipe不止用於文本數據,還可以拿來處理比如圖片。當你有一個程式可以接收並處理二進位圖片,然後輸出一個二進位圖片的時候,可以像這樣把它連進去,你甚至可以這樣處理視頻。
一個功能全面又強大的工具
- root用戶
在linux和Mac OS中,root用戶類似於Windows的管理員(Administrator),有值為0的用戶ID。
root用戶允許在系統上做任意行為。就算一個文件中任何人都不可讀的或者任何人都不可寫的,root卻可以訪問這個文件並且讀寫。多數情況下,應該使用一個普通用戶來操作電腦,因為root具有風險,比如在root下運行了一個錯誤的程式,可能會毀掉你的整個電腦。
- sudo
但是如果在普通用戶下需要使用root許可權操作時,可以使用sudo命令,這可以讓你使用超級用戶許可權運行程式。
sudo的通常用法是,sudo 需要調用的命令
應用場景:
在你的電腦中有很多特別的文件系統,例如sysfs。我們進入到在/sys目錄,這個文件系統不是真實存在的文件,相反,這是一堆內核參數。內核(kernel)基本上就是你電腦(操作系統)的核心。
[lighthouse@VM-8-17-centos sys]$ ls
block bus class dev devices firmware fs hypervisor kernel module power
通過這些像是文件系統的東西,可以訪問到內核的參數。
由於這些內核參數是以文件形式展露的,我們可以使用先前的所有工具去操作它們。例如:
你可以在/sys/class/backlight/intel_backlight/下的brightness操作背光亮度
但是如果直接操作,會顯示拒絕訪問,因為內核的東西基本上都要root許可權。
但是如果運行命令sudo echo 500 > brightness
,依然顯示沒有許可權
因為輸入輸出的重定向是程式不知道的,管道和重定向都是Shell設好的,現在的情況是,我告訴Shell去運行sudo,並且包括參數echo 500 ,然後發送輸出到brightness這個文件。也就是說,sudo的root許可權只給了前面的echo命令。Shell打開brightness的時候,用的不是sudo,因此顯示沒有許可權。
因此,現在的解決方法是:
方法一:切換到root終端,sudo su
su命令能讓你以超級用戶登錄shell
使用超級用戶登錄後,可以看到提示符從$
變成了#
。然後運行echo 500 > brightness
,屏幕的亮度變暗了,並且沒有出現許可權不足提示。因為現在Shell以root身份運行,root用戶允許打開該內核文件。
方法二:使用管道和重定向
Shell去運行echo 1060
,會輸出1060,然後告訴Shell去運行sudo tee brightness
命令,然後把echo的輸出送入tee的輸入,然後tee打開brightness文件(tee程式以root許可權運行),並將tee的輸入流寫入到brightness文件和標準輸出流(這裡是終端)
tee命令取它的輸入,然後寫入到一個文件,並且寫入到標準輸出流
tee - read from standard input and write to standard output and files
使用方法二可以毋需登錄到root用戶。
可以在其他需要root許可權的地方法使用這種方法:
例如我現在想讓鍵盤上的滾動鎖定燈亮起來,該內核文件在/sys/class/leds/input1::scrolllock/brightness
使用同樣的方法,將參數由0變為1
[lighthouse@VM-8-17-centos input1::scrolllock]$ ls
brightness device max_brightness power subsystem trigger uevent
[lighthouse@VM-8-17-centos input1::scrolllock]$ cat brightness
0
[lighthouse@VM-8-17-centos input1::scrolllock]$ echo 1 | tee brightness
tee: brightness: Permission denied
1
[lighthouse@VM-8-17-centos input1::scrolllock]$ echo 1 |sudo tee brightness
1
現在鍵盤上的滾動鎖定燈已經亮起來了
- 打開文件
xdg-open命令,這個指令可能只在linux上運行,在Mac Os上可能叫做open
你給出一個文件名,然後xdg-open就會使用合適的程式打開它
練習
-
本課程需要使用類Unix shell,例如 Bash 或 ZSH。使用
echo $SHELL
命令可以查看您的 shell 是否滿足要求。如果列印結果為/bin/bash
或/usr/bin/zsh
則是可以的。[lighthouse@VM-8-17-centos tmp]$ echo $SHELL /bin/bash
-
在
/tmp
下新建一個名為missing
的文件夾。[lighthouse@VM-8-17-centos tmp]$ mkdir missing
-
用
man
查看程式touch
的使用手冊。[lighthouse@VM-8-17-centos tmp]$ man touch
-
用
touch
在missing
文件夾中新建一個叫semester
的文件。[lighthouse@VM-8-17-centos tmp]$ touch ./missing/semester
-
將以下內容一行一行地寫入
#!/bin/sh curl --head --silent https://missing.csail.mit.edu
第一行可能有點棘手,
#
在Bash中表示註釋,而!
即使被雙引號("
)包裹也具有特殊的含義。 單引號('
)則不一樣,此處利用這一點解決輸入問題。更多信息請參考 Bash quoting 手冊[lighthouse@VM-8-17-centos missing]$ echo '#!/bin/sh' > semester [lighthouse@VM-8-17-centos missing]$ echo "curl --head --silent https://missing.csail.mit.edu" >> semester [lighthouse@VM-8-17-centos missing]$ cat semester #!/bin/sh curl --head --silent https://missing.csail.mit.edu
-
嘗試執行這個文件。例如,將該腳本的路徑(
./semester
)輸入到您的shell中並回車。如果程式無法執行,請使用ls
命令來獲取信息並理解其不能執行的原因。[lighthouse@VM-8-17-centos missing]$ ./semester -bash: ./semester: Permission denied [lighthouse@VM-8-17-centos missing]$ ls -l total 4 -rw-rw-r-- 1 lighthouse lighthouse 60 Dec 30 23:44 semester
原因是沒有執行x許可權
-
查看
chmod
的手冊(例如,使用man chmod
命令)略
-
使用
chmod
命令改變許可權,使./semester
能夠成功執行,不要使用sh semester
來執行該程式。您的 shell 是如何知曉這個文件需要使用sh
來解析呢?更多信息請參考:shebang[lighthouse@VM-8-17-centos missing]$ chmod 764 semester [lighthouse@VM-8-17-centos missing]$ ./semester HTTP/1.1 200 OK Connection: keep-alive Content-Length: 7991 Server: GitHub.com Content-Type: text/html; charset=utf-8 Last-Modified: Mon, 05 Dec 2022 15:59:23 GMT Access-Control-Allow-Origin: * ETag: "638e155b-1f37" expires: Tue, 27 Dec 2022 02:31:08 GMT Cache-Control: max-age=600 x-proxy-cache: MISS X-GitHub-Request-Id: 5400:19D5:CB919:12261D:63AA5694 Accept-Ranges: bytes Date: Fri, 30 Dec 2022 15:59:50 GMT Via: 1.1 varnish Age: 0 X-Served-By: cache-nrt-rjtf7700066-NRT X-Cache: HIT X-Cache-Hits: 1 X-Timer: S1672415990.322601,VS0,VE211 Vary: Accept-Encoding X-Fastly-Request-ID: b5ca5ecd45fb43becb00f6f5b089c1d56b46a765
-
使用
|
和>
,將semester
文件輸出的最後更改日期信息,寫入主目錄下的last-modified.txt
的文件中[lighthouse@VM-8-17-centos missing]$ ./semester | grep Last > ~/last-modified.txt [lighthouse@VM-8-17-centos missing]$ cat ~/last-modified.txt Last-Modified: Mon, 05 Dec 2022 15:59:23 GMT
-
寫一段命令來從
/sys
中獲取筆記本的電量信息,或者台式機 CPU 的溫度。註意:macOS 並沒有 sysfs,所以 Mac 用戶可以跳過這一題。略