主題 1 The Shell

一刀一個小西瓜發表於2022-12-31

主題 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代表檔案被授予的許可權。

image-20221222225758192

閱讀這一串字元的方法如下: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)

  1. 預設輸入流裡的內容來自你的鍵盤,基本上輸入流是終端,無論你向終端輸入什麼,最後都會傳到程式裡。

  2. 預設的輸出流(即當程式想要輸出一些內容時),預設也是終端

    這也是為什麼當你在終端中打入echo hello時,hello會直接顯示在你的終端裡

Shell提供了重定向這些流的方法,把輸入和輸出都改到程式設計師指明的地方。最直接的方式就是使用大於小於號(即所謂的尖角括號)。

  1. 小於號表示重定向這個程式的輸入流
  2. 大於號表示重定向程式的輸出流

例如:

[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操作背光亮度

image-20221223021905164

但是如果直接操作,會顯示拒絕訪問,因為核心的東西基本上都要root許可權。

但是如果執行命令sudo echo 500 > brightness,依然顯示沒有許可權

image-20221223022224630

因為輸入輸出的重定向是程式不知道的,管道和重定向都是Shell設好的,現在的情況是,我告訴Shell去執行sudo,並且包括引數echo 500 ,然後傳送輸出到brightness這個檔案。也就是說,sudo的root許可權只給了前面的echo命令。Shell開啟brightness的時候,用的不是sudo,因此顯示沒有許可權。

因此,現在的解決方法是:

方法一:切換到root終端,sudo su

su命令能讓你以超級使用者登入shell

使用超級使用者登入後,可以看到提示符從$變成了#。然後執行echo 500 > brightness,螢幕的亮度變暗了,並且沒有出現許可權不足提示。因為現在Shell以root身份執行,root使用者允許開啟該核心檔案。

image-20221223023516151

方法二:使用管道和重定向

image-20221223024934470

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就會使用合適的程式開啟它

練習

  1. 本課程需要使用類Unix shell,例如 Bash 或 ZSH。使用echo $SHELL命令可以檢視您的 shell 是否滿足要求。如果列印結果為/bin/bash/usr/bin/zsh則是可以的。

    [lighthouse@VM-8-17-centos tmp]$ echo $SHELL
    /bin/bash
    
  2. /tmp 下新建一個名為 missing 的資料夾。

    [lighthouse@VM-8-17-centos tmp]$ mkdir missing
    
  3. man 檢視程式 touch 的使用手冊。

    [lighthouse@VM-8-17-centos tmp]$ man touch
    
  4. touchmissing 資料夾中新建一個叫 semester 的檔案。

    [lighthouse@VM-8-17-centos tmp]$ touch ./missing/semester
    
  5. 將以下內容一行一行地寫入

     #!/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
    
  6. 嘗試執行這個檔案。例如,將該指令碼的路徑(./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許可權

  7. 檢視 chmod 的手冊(例如,使用 man chmod 命令)

  8. 使用 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
    
    
  9. 使用 |> ,將 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
    
  10. 寫一段命令來從 /sys 中獲取筆記本的電量資訊,或者桌上型電腦 CPU 的溫度。注意:macOS 並沒有 sysfs,所以 Mac 使用者可以跳過這一題。

相關文章