主題 2 Shell工具和腳本

来源:https://www.cnblogs.com/liyuelian/archive/2023/01/03/17023580.html
-Advertisement-
Play Games

主題 2 Shell工具和腳本 Shell 工具和腳本 · the missing semester of your cs education (missing-semester-cn.github.io) Shell腳本 shell 腳本是一種更加複雜度的工具。 定義變數 在bash中為變數賦值的 ...


主題 2 Shell工具和腳本

Shell 工具和腳本 · the missing semester of your cs education (missing-semester-cn.github.io)

Shell腳本

shell 腳本是一種更加複雜度的工具。

  • 定義變數

在bash中為變數賦值的語法是foo=bar,意為定義變數foo,foo的值為bar。訪問變數使用$變數名

[lighthouse@VM-8-17-centos tools]$ foo=bar
[lighthouse@VM-8-17-centos tools]$ echo "$foo"
bar

需要註意的是,Shell中使用空格作為分隔參數的保留字元。

如果將上訴賦值語句寫為foo = bar,將不起作用。事實上,這樣寫並沒有將bar賦給foo,而是用=bar作為參數調用foo程式。因為這樣Shell會認為你正在執行一個名為foo的命令。

[lighthouse@VM-8-17-centos tools]$ foo = bar
-bash: foo: command not found

你需要特別註意這類問題,比如如果有帶空格的文件名,你需要使用引號將其括起來。

  • 在bash中處理字元串

有兩種定義字元串的方法,可以使用雙引號定義字元串,也可以使用單引號定義字元串。

[lighthouse@VM-8-17-centos tools]$ echo "Hello"
Hello
[lighthouse@VM-8-17-centos tools]$ echo 'Hello'
Hello

Bash中的字元串通過'"分隔符來定義,但是它們的含義並不相同。

'定義的字元串為原義字元串,其中的變數不會被轉義,而 "定義的字元串會將變數值進行替換。

例如:

[lighthouse@VM-8-17-centos tools]$ echo "Value is $foo"
Value is bar
[lighthouse@VM-8-17-centos tools]$ echo 'Value is $foo'
Value is $foo
  • 定義函數

和其他大多數的編程語言一樣,bash也支持if, case, whilefor 這些控制流關鍵字。同樣地, bash 也支持函數,它可以接受參數並基於參數進行操作。

下麵這個函數是一個例子,它會創建一個文件夾並使用cd進入該文件夾。

[lighthouse@VM-8-17-centos tools]$ cat mcd.sh
mcd(){
	mkdir -p "$1"
	cd "$1"
}

這裡 $1 是腳本的第一個參數的意思

source 腳本名,這將會在Shell中載入腳本並運行。

[lighthouse@VM-8-17-centos tools]$ source mcd.sh
[lighthouse@VM-8-17-centos tools]$ mcd test
[lighthouse@VM-8-17-centos test]$ 

如上,在執行了source mcd.sh之後,看似無事發生,但實際上Shel中已經定義了mcd函數。我們給mcd傳遞一個參數test,這個參數被用於作為創建的目錄名(即$1),然後Shell自動切換到了test目錄里。整個過程就是,我們創建了文件夾併進入其中。

  • 保留字

在bash中,許多$開頭的東西一般都是被保留的(指留作特定用途)

$1 是腳本的第一個參數的意思。與其他腳本語言不同的是,bash使用了很多特殊的變數來表示參數、錯誤代碼和相關變數。下麵列舉其中一些變數,更完整的列表可以參考 這裡

形式 釋義
$0 腳本名
$1 ~ $9 腳本的參數, $1 是第一個參數,依此類推
$@ 所有參數
$# 參數個數
$? 前一個命令的返回值
$$ 當前腳本的進程識別碼
!! 完整的上一條命令,包括參數。常見應用:當你因為許可權不足執行命令失敗時,可以使用 sudo !!再嘗試一次。
$_ 上一條命令的最後一個參數,如果你正在使用的是互動式 shell,你可以通過按下 Esc 之後鍵入 . 來獲取這個值。

有一些保留字可以直接在Shell中使用,例如$?可以獲取上一條命令的錯誤代碼(返回值),再比如$_會返回上一條命令的最後一個參數。

例如:

[lighthouse@VM-8-17-centos tools]$ mkdir test
[lighthouse@VM-8-17-centos tools]$ cd $_
[lighthouse@VM-8-17-centos test]$ 

如上,我們無需在寫一次test,使用$_訪問該參數,它就會被替換成test,現在我們進入到test目錄中了。

這樣的例子有很多,再例如!!,它返回完整的上一條命令,包括參數。常見應用:當你因為許可權不足執行命令失敗時,可以使用 sudo !!再嘗試一次。

[lighthouse@VM-8-17-centos tools]$ mkdir /mnt/new
mkdir: cannot create directory ‘/mnt/new’: Permission denied
[lighthouse@VM-8-17-centos tools]$ sudo !!
sudo mkdir /mnt/new
[lighthouse@VM-8-17-centos tools]$ rmdir /mnt/new
rmdir: failed to remove '/mnt/new': Permission denied
[lighthouse@VM-8-17-centos tools]$ sudo !!
sudo rmdir /mnt/new
[lighthouse@VM-8-17-centos tools]$ 
  • 標準錯誤流

如果你的程式出錯了,你想輸出錯誤但不想污染標準輸出,那麼你可以寫進這個流。

  • 錯誤代碼

還有一種叫做錯誤代碼$?(error code)的東西,是一種告訴你整個運行過程結果如何的方式。

[lighthouse@VM-8-17-centos tools]$ echo "Hello"
Hello
[lighthouse@VM-8-17-centos tools]$ echo $?
0

這裡顯示echo "Hello" 運行的錯誤代碼為0,0是因為一切正常,沒有出現問題。

這種退出碼和如C語言里代表的意思一樣。

0代表一切正常,沒有出現錯誤。

[lighthouse@VM-8-17-centos tools]$ grep foobar mcd.sh
[lighthouse@VM-8-17-centos tools]$ echo $?
1

如上,我們嘗試著在mcd.sh腳本中查找foobar字元串,而它不存在,所以grep什麼都沒輸出。但是通過反饋一個1的錯誤代碼,它讓我們知道這件事沒有成功。

此外,true的錯誤代碼始終是0;false的錯誤代碼則是1。

[lighthouse@VM-8-17-centos tools]$ true
[lighthouse@VM-8-17-centos tools]$ echo $?
0
[lighthouse@VM-8-17-centos tools]$ false
[lighthouse@VM-8-17-centos tools]$ echo $?
1
  • 邏輯運算符

下麵bash要做的是執行第一個命令,如果第一個命令失敗,再去執行第二個(短路運演算法則)。因為它嘗試做一個邏輯或,如果第一個命令沒有0錯誤碼,就會去執行第二個命令

[lighthouse@VM-8-17-centos tools]$ false || echo "Oops fail"
Oops fail

相似地,如果我們把false換成true,那麼將不會執行第二個命令,因為第一個命令已經返回一個0錯誤碼了,第二個命令將會被短路。

[lighthouse@VM-8-17-centos tools]$ true || echo "Oops fail"
[lighthouse@VM-8-17-centos tools]$ 

相似的,我們使用與運算符&&,它僅當第一個命令執行無錯誤時,才會執行第二個部分。如果第一個命令失敗,那麼第二個命令就不會被執行。

[lighthouse@VM-8-17-centos tools]$ true && echo "Things went well"
Things went well
[lighthouse@VM-8-17-centos tools]$ false && echo "This will not print"
[lighthouse@VM-8-17-centos tools]$ 

使用;號連接的代碼,無論你執行什麼,都可以通過。在同一行使用分號來連接命令,如下,它始終會被列印出來。

[lighthouse@VM-8-17-centos tools]$ false ; echo "This will always print"
This will always print
  • 把命令的輸出存到變數里

這裡我們獲取pwd命令的輸出,它會列印出我們當前的工作路徑,然後把其存入foo變數中。然後我們詢問變數foo的值,我們就可以看到這個字元串

[lighthouse@VM-8-17-centos tools]$ foo=$(pwd)
[lighthouse@VM-8-17-centos tools]$ echo $foo
/home/lighthouse/missing-semester/tools

更廣泛地來說,我們可以通過一個叫做命令替換的東西,把它放進任意字元串中。並且因為我們使用的不是單引號,所以這串東西會被展開。

[lighthouse@VM-8-17-centos tools]$ echo "We are in $(pwd)"
We are in /home/lighthouse/missing-semester/tools
  • 過程替換

另一個比較好用知名度更低的東西叫做過程替換。和之前的命令替換是類似的,例如

[lighthouse@VM-8-17-centos tools]$ cat <(ls) <(ls ..)
mcd.sh
test
tools

如上,<(ls) <(ls ..)的作用是,()內部的命令會被執行,其輸出將被存儲到一個臨時文件內,然後把文件的標識符handle交給最左邊的命令。

因此,這裡我們在ls這個目錄,把輸出放到臨時文件內,再對父目錄如法炮製,然後把兩個文件連接。

這種寫法非常方便,因為有些命令會從某個文件的內容,而不是從標準輸入里,獲得輸入參數

綜合案例:

現在來看一個裡麵包含這些內容的簡單示例腳本:

example.sh

#!/bin/bash
  
echo "Start program at $(date)" # Date will be substituted

echo "Running program $0 with $# arguments with pid $$"

for file in "$@";do
        grep foobar "$file" > /dev/null 2> /dev/null
        # When pattern is not found,grep has exit status
        # We redirect STDOUT and STDERR to a null register ..
        if [[ "$?" -ne 0 ]]; then
                echo "File $file does not have any foobar, adding one"
                echo "# foobar" >> "$file"
        fi      
done

第三行:有一個$(date)的參數,date列印出當前的時間。

第五行:$0代表著當前運行的腳本的名稱,$#代表給定的參數個數,$$是這個命令的進程ID,一般縮寫為PID。

第七行:$@可以展開成所有參數,比如有三個參數,你可以鍵入$1 $2 $3,如果你不知道有多少個參數,也可以直接鍵入$@。這裡我們通過這種方式將所有參數放在這裡,然後這些參數被傳給for迴圈,for迴圈會創建一個file變數,依次地用這些參數賦值給file變數。

第八行:我們運行grep命令,它會在一堆文件里搜索一個子串。這裡我們在文件里搜索字元串foobar,文件變數file將會展開為賦給它的值。

之前說過,如果我們在意程式的輸出的話,我們可以把它重定向到某處(比如到一個文件裡面保存下來,或者連接組合)。但有時候情況恰恰相反,例如有時候我們只想知道某個腳本的錯誤代碼是什麼,例如這裡想知道grep能不能成功查找。我們並不在意程式的運行結果,因此我們甚至能直接扔掉整個輸出,包括標準輸出和標準錯誤流。這裡我們做的就是把兩個輸出重定向到/dev/null,/dev/null是UNIX系統的一種特殊設備,輸入到它的內容會被丟棄(就是說你可以隨意亂寫亂畫,然後所有的內容都會被丟掉)。

這裡的>代表重定向輸出流,2>代表重定向標準錯誤流(因為這兩個流是分立的,所以你要告訴bash去操作哪一個)。

所以這裡我們執行命令,去檢查文件有沒有foobar字元串,如果有的話,返回一個0錯誤代碼,如果沒有返回一個非0錯誤代碼。

第十一行:我們獲取前一個命令的錯誤代碼($?),然後是一個比較運算符-ne(代表不等於Non Equal)

其他編程式語言中有像=和≠,bash里有很多預設的比較運算(可以使用命令man test查看),這主要是為了你用Shell的時候,有很多東西要去測試。比如我們現在正在對比兩個數,看它們是否相同。

如果文件中沒有foobar,前一個命令將會返回一個非零錯誤代碼。

第十二行:我們將會如果前一個命令返回一個非0錯誤代碼,我們將會輸出一句話File xxx does not have any foobar, adding one

第十三行:使用>>往對應文件中追加一行註釋# foobar

現在我們來運行這個腳本,當前目錄下有一些文件,我們將這些文件作為參數傳給example.sh,檢查是否有foobar。

[lighthouse@VM-8-17-centos tools]$ ls
example.sh  hello.txt  mcd.sh
[lighthouse@VM-8-17-centos tools]$ ./example.sh hello.txt mcd.sh
Start program at Sun Dec 25 23:06:13 CST 2022
Running program ./example.sh with 2 arguments with pid 2570038
File hello.txt does not have any foobar, adding one
File mcd.sh does not have any foobar, adding one

我們在文件hello.txt和mcd.sh中沒有找到foobar字元串,因此腳本分別給這兩個文件添加了一個# foobar 註釋

[lighthouse@VM-8-17-centos tools]$ cat hello.txt
hello,this is a txt file
# foobar
[lighthouse@VM-8-17-centos tools]$ cat mcd.sh 
mcd(){
	mkdir -p "$1"
	cd "$1"
}
# foobar
  • 通配符

如果我們不想一個一個查找文件,可以使用通配符來進行匹配。

比如這裡*匹配任意字元,這裡將會顯示出所有含有任意字元,並以.sh結尾的文件

[lighthouse@VM-8-17-centos tools]$ ls
example.sh  hello.txt  image.png  mcd.sh  project1  project2  test
[lighthouse@VM-8-17-centos tools]$ ls *.sh
example.sh  mcd.sh

現在如果我只想找有一個而不是兩個特定字元的項,可以使用??匹配一個字元

[lighthouse@VM-8-17-centos tools]$ ls
example.sh  hello.txt  image.png  mcd.sh  project1  project2  project42  test
[lighthouse@VM-8-17-centos tools]$ ls project?
project1:
src

project2:
src

現在我們得到了匹配的目錄project1和project2

src是匹配的目錄下的子項

總而言之,通配符非常強大,你也可以組合它們。

一個常用模式是花括弧{}

比如目錄下有一個image.png圖片,我們想轉變該圖像的格式,一般的做法是convert image.png image.jpg,但是你也可以鍵入convert image.{png,jpg},它會展開成上面的那行。

又如:

[lighthouse@VM-8-17-centos tools]$ touch foo{,1,2,10}
[lighthouse@VM-8-17-centos tools]$ ls
example.sh  foo  foo1  foo10  foo2  hello.txt  mcd.sh project1  project2 test

如上所述,我們可以touch一串foo,所有的foo都會被展開。

你也可以進行多層操作,建立笛卡爾系:

[lighthouse@VM-8-17-centos tools]$ cat <(ls project?/src/test)
project1/src/test:

project2/src/test:
[lighthouse@VM-8-17-centos tools]$ touch project{1,2}/src/test/test{1,2,3}.py
[lighthouse@VM-8-17-centos tools]$ cat <(ls project?/src/test)
project1/src/test:
test1.py
test2.py
test3.py

project2/src/test:
test1.py
test2.py
test3.py

如上,我們在創建文件的路徑上有兩組花括弧,這會用兩組展開式形成笛卡爾積,意味著展開後所有的路徑有2*3組。因此當我們運行命令touch project{1,2}/src/test/test{1,2,3}.py時,實際上分別在./project1/src/test/目錄下和./project2/src/test/目錄下創建了test1.pytest2.pytest3.py文件。

你也可以將*通配符和{}通配符結合,甚至用一些範圍表示,如

[lighthouse@VM-8-17-centos tools]$ mkdir foo bar
[lighthouse@VM-8-17-centos tools]$ touch {foo,bar}/{a..d}
[lighthouse@VM-8-17-centos tools]$ cat <(ls {foo,bar}/)
bar/:
a
b
c
d

foo/:
a
b
c
d

如上,這將會從foo/a一直到展開到foo/d,而bar目錄下同理。

  • diff

diff 命令用於比較文件的差異。diff 以逐行的方式,比較文本文件的異同處。如果指定要比較目錄,則 diff 會比較目錄中相同文件名的文件,但不會比較其中子目錄。

[lighthouse@VM-8-17-centos tools]$ touch foo/x bar/y
[lighthouse@VM-8-17-centos tools]$ diff <(ls foo) <(ls bar)
5c5
< x
---
> y

如上,x只在第一個文件夾里,而y只在第二個文件夾內。

  • 其他Shell腳本

目前為止我們只看了bash腳本,如果你喜歡其他腳本(bash對一些工作可能並不是最好的選擇),你可以用很多語言寫和Shell工具交互的腳本。註意,腳本並不一定只有用 bash 寫才能在終端里調用。比如說,這是一段 Python 腳本,作用是將輸入的參數倒序輸出:

#!/usr/local/bin/python
import sys
for arg in reversed(sys.argv[1:]):
    print(arg)

如上,python預設不會嘗試和Shell交互,所以我們需要導入一些庫import sys。第一行叫做shebang,Shell通過它瞭解怎麼運行這個程式。

shebang這個單詞源於這行以#!開頭,#是sharp,!是bang

你可以隨時鍵入類似python script.py a b c的命令來運行這個python腳本:

[lighthouse@VM-8-17-centos tools]$ python script.py a b c
c
b
a

但是如果想讓它從Shell就能執行呢?這就需要用到shebang行。Shell用首行識別到需要用Python解釋器運行這個程式,並且第一行給出了python解釋器所在的路徑。

[lighthouse@VM-8-17-centos tools]$ ./script.py a b c
c
b
a

需要註意的是不同的設備很可能會把python放在不同的地方,最好不要假設文件放在固定的位置,其他的東西要是如此。

shebang 行中使用 env 命令,會根據給出的參數(這裡是python),env 會利用之前的PATH 環境變數來進行定位,在此路徑中找python二進位文件,然後用該文件去解釋這個腳本。這會有更好的可移植性

#!/usr/bin/env python
import sys
for arg in reversed(sys.argv[1:]):
    print(arg)
  • shellcheck

編寫bash腳本有時候會很彆扭和反直覺。例如 shellcheck 這樣的工具可以幫助你定位sh/bash腳本中的錯誤。

koalaman/shellcheck at v0.7.1 (github.com)

shellcheck可以給出warning和語法錯誤提示,還能指出哪些地方你沒正確引用等。

[lighthouse@VM-8-17-centos tools]$ shellcheck mcd.sh

In mcd.sh line 1:
mcd(){
^-- SC2148: Tips depend on target shell and yours is unknown. Add a shebang.

  • Shell函數和腳本的區別

shell函數和腳本有如下一些不同點:

  1. 函數只能與shell使用相同的語言,腳本可以使用任意語言。因此在腳本中包含 shebang 是很重要的。
  2. 函數僅在定義時被載入,腳本會在每次被執行時載入。這讓函數的載入比腳本略快一些,但每次修改函數定義,都要重新載入一次。
  3. 函數會在當前的shell環境中執行,腳本會在單獨的進程中執行。因此,函數可以對環境變數進行更改,比如改變當前工作目錄,腳本則不行。腳本需要使用 export 將環境變數導出,並將值傳遞給環境變數。
  4. 與其他程式語言一樣,函數可以提高代碼模塊性、代碼復用性並創建清晰性的結構。shell腳本中往往也會包含它們自己的函數定義。

Shell工具

查看命令如何使用

  • man命令

給出一個命令,應該怎樣瞭解如何使用這個命令行並找出它的不同的選項呢?最常用的方法是為對應的命令行添加-h--help 標記。另外一個更詳細的方法則是使用man 命令。man命令是手冊(manual)的縮寫,它提供了命令的用戶手冊。

事實上,目前我們給出的所有命令的說明鏈接,都是網頁版的Linux命令手冊,即使是安裝的第三方命令。當然前提是開發者編寫了手冊並將其包含在了安裝包中。在互動式的、基於字元處理的終端視窗中,一般也可以通過 :help 命令或鍵入 ? 來獲取幫助。

  • tldr (too long don't read)

有時候手冊內容太過詳實,讓我們難以在其中查找哪些最常用的標記和語法。TLDR pages是一個很不錯的替代品,它提供了一些案例,可以幫助你快速找到正確的選項。

下載tldr:npm install -g tldr

使用npm命令之前要先下載 yum -y install npm

查找文件

你當然可以使用ls,但是如果你想查找一個已經知道名字的文件或者目錄,我們可以有更好的做法

  • find

linux-find

find大概是每個UNIX系統都有的工具,例如

[lighthouse@VM-8-17-centos tools]$ find . -name src -type d
./project1/src
./project2/src

這裡意為,在當前文件夾.調用find,查找名為src 並且類型為目錄的東西。鍵入以上命令,它就可以在當前目錄遞歸查看所有符合規則的文件或者文件夾(find預設遞歸指定目錄)。

find還有許多有用的flag,比如你甚至可以查詢指定格式的文件路徑:

[lighthouse@VM-8-17-centos tools]$ find . -path '**/test/*.py' -type f
./project1/src/test/test2.py
./project1/src/test/test1.py
./project1/src/test/test3.py
./project2/src/test/test2.py
./project2/src/test/test1.py
./project2/src/test/test3.py

這裡**是指可以匹配零或者多個目錄名,然後在此路徑下找到拓展名為.py的文件,並要求它們在一個test文件夾內,同時檢查它是否為F類型(f代表文件file)

運用不用的flag,可以進行非路徑和非文件名的篩選:

比如可以查找被修改過的文件,這裡-mtime代表修改時間,.當前目錄下,最近1天被修改過的東西都會被列出

[lighthouse@VM-8-17-centos tools]$ find . -mtime -1
.
./project1
./project1/src
./project1/src/test
./project1/src/test/test2.py
./project1/src/test/test1.py
./project1/src/test/test3.py
./project2
./project2/src
./project2/src/test
./project2/src/test/test2.py
./project2/src/test/test1.py
./project2/src/test/test3.py
./test
./mcd.sh

你甚至可以使用其他條件,比如大小,所有者,許可權等等。

強大的是,find不僅可以查找東西,找到之後還可以做別的:例如

我們可以在當前目錄下查找所有擴展名為.tmp的文件,然後要求find對於所有這些文件,執行rm命令

[lighthouse@VM-8-17-centos tools]$ find . -name "*.tmp"
./project1/src/test/test3.tmp
./project1/src/test/test1.tmp
./project1/src/test/test2.tmp
./project2/src/test/test3.tmp
./project2/src/test/test1.tmp
./project2/src/test/test2.tmp
[lighthouse@VM-8-17-centos tools]$ find . -name "*.tmp" -exec rm {} \;
[lighthouse@VM-8-17-centos tools]$ echo $?
0
[lighthouse@VM-8-17-centos tools]$ find . -name "*.tmp"
[lighthouse@VM-8-17-centos tools]$ 

如上,執行find . -name "*.tmp" -exec rm {} \;後,對應的tmp文件都被刪除了。

  • fd

fd 是一個更簡單、更快速、更友好的程式,它可以用來作為find的替代品。它有很多不錯的預設設置,例如輸出著色、預設支持正則匹配、支持unicode並且我認為它的語法更符合直覺。以模式PATTERN 搜索的語法是 fd PATTERN

[lighthouse@VM-8-17-centos tools]$ fd ".*py"
project1/src/test/test1.py
project1/src/test/test2.py
project1/src/test/test3.py
project2/src/test/test1.py
project2/src/test/test2.py
project2/src/test/test3.py
  • locate

大多數人都認為 findfd 已經很好用了,但是有的人可能想知道,我們是不是可以有更高效的方法,例如不要每次都搜索文件而是通過編譯索引或建立資料庫的方式來實現更加快速地搜索。

這就要靠 locate 了。 locate 使用一個由 updatedb負責更新的資料庫,在大多數系統中 updatedb 都會通過 cron 每日更新。這便需要我們在速度和時效性之間作出權衡。而且,find 和類似的工具可以通過別的屬性比如文件大小、修改時間或是許可權來查找文件,locate則只能通過文件名。 這裡有一個更詳細的對比。

查找代碼

查找文件是很有用的技能,但是很多時候你的目標其實是查看文件的內容。常見的場景是查找具有匹配某種模式的全部文件,並找它們的位置。

  • grep

grep是用於對輸入文本進行匹配的通用工具。

[lighthouse@VM-8-17-centos tools]$ grep foobar mcd.sh 
# foobar

使用-R可以遞歸地搜索

[lighthouse@VM-8-17-centos tools]$ grep -R foobar .
./example.sh:        grep foobar "$file" > /dev/null 2> /dev/null
./example.sh:                echo "File $file does not have any foobar, adding one"
./example.sh:                echo "# foobar" >> "$file"
./hello.txt:# foobar
./mcd.sh:# foobar

grep 有很多選項,這也使它成為一個非常全能的工具。 -C :獲取查找結果的上下文(Context);-v 將對結果進行反選(Invert),也就是輸出不匹配的結果。舉例來說, grep -C 5 會輸出匹配結果前後五行。當需要搜索大量文件的時候,使用 -R 會遞歸地進入子目錄並搜索所有的文本文件。但是也有很多辦法可以對 grep -R 進行改進,例如使其忽略.git 文件夾,使用多CPU等等。

  • rg(ripgrep)

此外還出現了很多grep的替代品,包括 ack, agrg。它們都特別好用,但是功能也都差不多,比較常用的是 ripgrep (rg) ,因為它速度快,而且用法非常符合直覺。

rg安裝

[lighthouse@VM-8-17-centos tools]$ rg "foobar" -t sh ~/
/home/lighthouse/missing/tools/mcd.sh
5:# foobar

/home/lighthouse/missing/tools/example.sh
8:        grep foobar "$file" > /dev/null 2> /dev/null
12:                echo "File $file does not have any foobar, adding one"
13:                echo "# foobar" >> "$file"

如上,該命令在~/目錄下搜索類型(-t即type)為sh,並且文件內有“foobar”子串的文件。

rg不僅能找到對應文件,還能精確到匹配的行,比起使用grep,它還增加了代碼彩色顯示和文件處理啥的,也有Unicode支持,並且運行很快。

rg有許多有用的flag,比如說你想要點上下文(匹配內容的附近內容),例如:

[lighthouse@VM-8-17-centos tools]$ rg "foobar" -t sh -C 5 ~/
/home/lighthouse/missing/tools/mcd.sh
1-mcd(){
2-	mkdir -p "$1"
3-	cd "$1"
4-}
5:# foobar

/home/lighthouse/missing/tools/example.sh
3-echo "Start program at $(date)" # Date will be substituted
4-
5-echo "Running program $0 with $# arguments with pid $$"
6-
7-for file in "$@";do
8:        grep foobar "$file" > /dev/null 2> /dev/null
9-        # When pattern is not found,grep has exit status
10-        # We redirect STDOUT and STDERR to a null register ..
11-        if [[ "$?" -ne 0 ]]; then
12:                echo "File $file does not have any foobar, adding one"
13:                echo "# foobar" >> "$file"
14-        fi      
15-done

如上,我們加上-C [num](C意為context),不僅能夠搜索到匹配內容,還能對每一個匹配的內容顯示其前後[num]行的內容。這樣你就可以知道匹配內容大概在什麼位置,它周圍都是什麼內容。這個功能在查找在哪調用了什麼函數 上十分有用。

我們也可以使用一個更高級的用法:

-u意為不忽略隱藏文件,--files-without-match是列印出所有不匹配這個pattern的內容,'#!'的意思是匹配有#!的內容。也就是說,我們在搜索沒有shebang的文件。

[lighthouse@VM-8-17-centos tools]$ rg -u --files-without-match '#!' -t sh
mcd.sh

此外rg還有些好用的flag,比如--stats這個flag,

[lighthouse@VM-8-17-centos tools]$ rg "foobar" -t sh -C 5 --stats ~/
/home/lighthouse/missing/tools/mcd.sh
1-mcd(){
2-	mkdir -p "$1"
3-	cd "$1"
4-}
5:# foobar

/home/lighthouse/missing/tools/example.sh
3-echo "Start program at $(date)" # Date will be substituted
4-
5-echo "Running program $0 with $# arguments with pid $$"
6-
7-for file in "$@";do
8:        grep foobar "$file" > /dev/null 2> /dev/null
9-        # When pattern is not found,grep has exit status
10-        # We redirect STDOUT and STDERR to a null register ..
11-        if [[ "$?" -ne 0 ]]; then
12:                echo "File $file does not have any foobar, adding one"
13:                echo "# foobar" >> "$file"
14-        fi      
15-done

4 matches
4 matched lines
2 files contained matches
5 files searched
643 bytes printed
978 bytes searched
0.000054 seconds spent searching
0.002657 seconds

如上,它除了搜索結果之外,還可以輸出一些信息。比如成功匹配了多少行,查找了多少行和多少文件,列印了多少byte等。

  • ack

ack也是grep的一個替代工具,還有ag 。當然這些工具都是可以替換的,只要會使用即可。

查找shell命令

  • 向上箭頭

首先,按向上的方向鍵會顯示你使用過的上一條命令,繼續按上鍵則會遍歷整個歷史記錄。

向上箭頭並不是很有效率,所以bash有一些更加簡單的方法。

  • history

它會列印出你的命令歷史記錄,當然一般來講這會輸出非常多的記錄,你可以使用管道和grep來篩選。

[lighthouse@VM-8-17-centos tools]$ history | grep echo
   74  2022-12-29 01:16:27 echo $?
  112  2022-12-29 01:45:37 echo "# foobar" >> mdc.sh
  115  2022-12-29 01:46:01 echo "# foobar" >> mcd.sh
  126  2022-12-29 01:50:42 echo "hello,i am a txt file" > hello.txt
  197  2022-12-30 01:06:13 history | grep echo
  • Ctrl+R

基本上,所有Shell都會預設把Ctrl+R這個組合鍵設成(按執行時間)倒敘搜索(backward search)

我們打開(按ctrl+r)倒敘搜索,然後輸入echo,就會找到與之匹配的命令,如果我們接著按ctrl+r,就會倒著往前搜索匹配的命令,也可以重新執行命令。

  • fzf

Ctrl+R 可以配合 fzf 使用。fzf 是一個通用對模糊查找工具,它可以和很多命令一起使用。這裡我們可以對歷史命令進行模糊查找並將結果以賞心悅目的格式輸出

  • 基於歷史的自動補全

另外一個和歷史命令相關的技巧我喜歡稱之為基於歷史的自動補全。 這一特性最初是由 fish shell 創建的,它可以根據你最近使用過的開頭相同的命令,動態地對當前對shell命令進行補全。這一功能在 zsh 中也可以使用,它可以極大的提高用戶體驗。

你可以修改 shell history 的行為,例如,如果在命令的開頭加上一個空格,它就不會被加進shell記錄中。當你輸入包含密碼或是其他敏感信息的命令時會用到這一特性。 為此你需要在.bashrc中添加HISTCONTROL=ignorespace或者向.zshrc 添加 setopt HIST_IGNORE_SPACE。 如果你不小心忘了在前面加空格,可以通過編輯。bash_history.zhistory 來手動地從歷史記錄中移除那一項。

文件夾導航

你可以使用ls -R遞歸地列出某目錄下所有的文件和目錄,但是這樣列出的東西比較難理解。

  • tree

有一個叫tree的工具可以以比較友好的格式列印出目錄的結構。

centos安裝:sudo yum -y install tree

[lighthouse@VM-8-17-centos tools]$ tree /home
/home
`-- lighthouse
    `-- missing
        `-- tools
            |-- example.sh
            |-- hello.txt
            |-- mcd.sh
            |-- project1
            |   `-- src
            |       `-- test
            |           |-- test1.py
            |           |-- test2.py
            |           `-- test3.py
            |-- project2
            |   `-- src
            |       `-- test
            |           |-- test1.py
            |           |-- test2.py
            |           `-- test3.py
            `-- test

10 directories, 9 files
  • broot

broot也是做差不多的事情,但是比起列出所有文件,它會提示[還有更多文件,未列出]。你可以輸入字元,broot可以模糊匹配符合條件的文件,併進行動態顯示。這樣你就可以快速的選擇和定位。

  • nnn

nnn 預設列出運行 nnn 的當前目錄的文件和文件夾。 文件夾列在頂部,而文件列在底部。而且是一個交互性的視窗,你可以通過向左箭頭返回上一級目錄,通過向右箭頭到達子目錄。按q即可退出視窗。

centos 安裝 nnn :sudo yum install nnn

image-20221230014444510
  • ranger

ranger 是一個基於文本的由 Python 編寫的文件管理器。不同層級的目錄分別在一個面板的三列中進行展示. 可以通過快捷鍵, 書簽, 滑鼠以及歷史命令在它們之間移動. 當選中文件或目錄時, 會自動顯示文件或目錄的內容。


由於本課程的目的是儘可能對你的日常習慣進行優化。因此,我們可以使用fasdautojump 這兩個工具來查找最常用或最近使用的文件和目錄。

Fasd 基於 frecency 對文件和文件排序,也就是說它會同時針對頻率(frequency)和時效(recency)進行排序。預設情況下,fasd使用命令 z 幫助我們快速切換到最常訪問的目錄。例如, 如果您經常訪問/home/user/files/cool_project 目錄,那麼可以直接使用 z cool 跳轉到該目錄。對於 autojump,則使用j cool代替即可。

練習

  1. 閱讀 man ls ,然後使用ls 命令進行如下操作:

    • 所有文件(包括隱藏文件)
    • 文件列印以人類可以理解的格式輸出 (例如,使用454M 而不是 454279954)
    • 文件以最近訪問順序排序
    • 以彩色文本顯示輸出結果

    典型輸出如下:

     -rw-r--r--   1 user group 1.1M Jan 14 09:53 baz
     drwxr-xr-x   5 user group  160 Jan 14 09:53 .
     -rw-r--r--   1 user group  514 Jan 14 06:42 bar
     -rw-r--r--   1 user group 106M Jan 13 12:12 foo
     drwx------+ 47 user group 1.5K Jan 12 18:08 ..
    

    練習:

    (1) 顯示包括隱藏文件

    -a, --all
    do not ignore entries starting with .

    [lighthouse@VM-8-17-centos tools]$ ls -a
    .  ..  example.sh  hello.txt  mcd.sh  project1  project2  test
    

    (2) 文件以人類可以理解的格式輸出

    -h, --human-readable
    with -l, print sizes in human readable format (e.g., 1K 234M 2G)

    [lighthouse@VM-8-17-centos tools]$ ls -hl
    total 24K
    -rwxrwxr-- 1 lighthouse lighthouse  494 Dec 29 01:49 example.sh
    -rw-rw-r-- 1 lighthouse lighthouse   31 Dec 29 01:55 hello.txt
    -rw-rwxr-- 1 lighthouse lighthouse   42 Dec 29 01:46 mcd.sh
    drwxrwxr-x 3 lighthouse lighthouse 4.0K Dec 29 00:47 project1
    drwxrwxr-x 3 lighthouse lighthouse 4.0K Dec 29 00:47 project2
    drwxrwxr-x 2 lighthouse lighthouse 4.0K Dec 29 00:55 test
    

    (3) 文件以最近訪問順序排序

    -t sort by modification time, newest first

    [lighthouse@VM-8-17-centos tools]$ ls -lt
    total 24
    -rw-rw-r-- 1 lighthouse lighthouse   31 Dec 29 01:55 hello.txt
    -rwxrwxr-- 1 lighthouse lighthouse  494 Dec 29 01:49 example.sh
    -rw-rwxr-- 1 lighthouse lighthouse   42 Dec 29 01:46 mcd.sh
    drwxrwxr-x 2 lighthouse lighthouse 4096 Dec 29 00:55 test
    drwxrwxr-x 3 lighthouse lighthouse 4096 Dec 29 00:47 project1
    drwxrwxr-x 3 lighthouse lighthouse 4096 Dec 29 00:47 project2
    

    (4) 以彩色文本顯示輸出結果

    --color[=WHEN]
    colorize the output; WHEN can be 'never', 'auto', or 'always' (the default); more info below

    [lighthouse@VM-8-17-centos tools]$ ls --color=auto
    example.sh  hello.txt  mcd.sh  project1  project2  test
    

    綜合:

    [lighthouse@VM-8-17-centos tools]$ ls -laht --color=auto
    total 32K
    drwxrwxr-x 5 lighthouse lighthouse 4.0K Dec 30 01:14 .
    -rw-rw-r-- 1 lighthouse lighthouse   31 Dec 29 01:55 hello.txt
    -rwxrwxr-- 1 lighthouse lighthouse  494 Dec 29 01:49 example.sh
    -rw-rwxr-- 1 lighthouse lighthouse   42 Dec 29 01:46 mcd.sh
    drwxrwxr-x 2 lighthouse lighthouse 4.0K Dec 29 00:55 test
    drwxrwxr-x 3 lighthouse lighthouse 4.0K Dec 29 00:47 project1
    drwxrwxr-x 3 lighthouse lighthouse 4.0K Dec 29 00:47 project2
    drwxrwxr-x 3 lighthouse lighthouse 4.0K Dec 29 00:46 ..
    
  2. 編寫兩個bash函數 marcopolo 執行下麵的操作。 每當你執行 marco 時,當前的工作目錄應當以某種形式保存,當執行 polo 時,無論現在處在什麼目錄下,都應當 cd 回到當時執行 marco 的目錄。 為了方便debug,你可以把代碼寫在單獨的文件 marco.sh 中,並通過 source marco.sh命令,(重新)載入函數。

    練習:

    marco.sh:

    marco(){
    echo "$(pwd)" > ~/pwd.txt
    }
    
    polo(){
    jump=$(cat ~/pwd.txt)
    # 使用$(命令)的方式可以賦給變數
    cd "$jump"
    echo "You had alread jump to -->$jump"
    }
    

    測試:

    [lighthouse@VM-8-17-centos tools]$ source marco.sh
    [lighthouse@VM-8-17-centos tools]$ marco
    [lighthouse@VM-8-17-centos tools]$ cd /
    [lighthouse@VM-8-17-centos /]$ polo
    You had alread jump to -->/home/lighthouse/missing/tools
    [lighthouse@VM-8-17-centos tools]$ 
    
  3. 假設您有一個命令,它很少出錯。因此為了在出錯時能夠對其進行調試,需要花費大量的時間重現錯誤並捕獲輸出。 編寫一段bash腳本,運行如下的腳本直到它出錯,將它的標準輸出和標準錯誤流記錄到文件,併在最後輸出所有內容。 加分項:報告腳本在失敗前共運行了多少次。

     #!/usr/bin/env bash
    
     n=$(( RANDOM % 100 ))
    
     if [[ n -eq 42 ]]; then
        echo "Something went wrong"
        >&2 echo "The error was using magic numbers"
        exit 1
     fi
    
     echo "Everything went according to plan"
    

    練習:

    上述腳本的意思是,取一個隨機數(RANDOM變數用於生成0~32767之前的任意隨機數),隨機數模100。如果結果等於42,就輸出兩句話,然後返回1退出碼;否則就輸出”Everything went according to plan“

    這裡的>&2的意思是 將標準輸出1和標準錯誤輸出2 都重定向到終端中(標準輸出或標準錯誤輸出的目的地預設都為終端)

    Linux shell標準輸入,標準輸出,錯誤輸出

    run.sh(buggy.sh為題目的腳本名)

     count=1
    
     while true
     do
         ./buggy.sh 1>> out.log 2>&1 #把stout和sterr一起重定向到out.log文件中(追加)
         if [[ $? -ne 0 ]]; then
    	 	 echo "運行錯誤,記錄在out.log中"
             echo "共運行 $count 次"
             break
         fi
         ((count++))
    
     done
    
    [lighthouse@VM-8-17-centos tools]$ ./run.sh 
    運行錯誤,記錄在out.log中
    共運行 82 次
    [lighthouse@VM-8-17-centos tools]$ ./run.sh 
    運行錯誤,記錄在out.log中
    共運行 42 次
    
  4. 本節課我們講解的 find 命令中的 -exec 參數非常強大,它可以對我們查找的文件進行操作。但是,如果我們要對所有文件進行操作呢?例如創建一個zip壓縮文件?我們已經知道,命令行可以從參數或標準輸入接受輸入。在用管道連接命令時,我們將標準輸出和標準輸入連接起來,但是有些命令,例如tar 則需要從參數接受輸入。這裡我們可以使用xargs 命令,它可以使用標準輸入中的內容作為參數。 例如 ls | xargs rm 會刪除當前目錄中的所有文件。

    您的任務是編寫一個命令,它可以遞歸地查找文件夾中所有的HTML文件,並將它們壓縮成zip文件。註意,即使文件名中包含空格,您的命令也應該能夠正確執行(提示:查看 xargs的參數-d,譯註:MacOS 上的 xargs沒有-d查看這個issue

    如果您使用的是 MacOS,請註意預設的 BSD findGNU coreutils 中的是不一樣的。你可以為find添加-print0選項,併為xargs添加-0選項。作為 Mac 用戶,您需要註意 mac 系統自帶的命令行工具和 GNU 中對應的工具是有區別的;如果你想使用 GNU 版本的工具,也可以使用 brew 來安裝

    練習:

    事先在當前文件夾下創建了一些html文件(包括帶有空格的he llo.html)

    [lighthouse@VM-8-17-centos question4]$ tree 
    .
    |-- he\ llo.html
    |-- index.html
    |-- project1
    |   |-- test
    |   |-- test1.html
    |   |-- test2.html
    |   `-- test3.html
    |-- project2
    |   |-- test
    |   |-- test1.html
    |   |-- test2.html
    |   `-- test3.html
    |-- test1.html
    |-- test2.html
    |-- test3.html
    |-- test4.html
    |-- test5.html
    |-- test6.html
    |-- test7.html
    |-- test8.html
    `-- test9.html
    
    4 directories, 17 files
    

    使用命令:

    [lighthouse@VM-8-17-centos question4]$ find .  -name "*.html" | xargs -d '\n' tar -cf html.zip
    

    查看壓縮包內容:

    可以看到包括有空格文件名的html在內全部壓縮成功

    [lighthouse@VM-8-17-centos question4]$ tar -tf html.zip 
    ./project1/test3.html
    ./project1/test2.html
    ./project1/test1.html
    ./test6.html
    ./test3.html
    ./test8.html
    ./test4.html
    ./test9.html
    ./project2/test3.html
    ./project2/test2.html
    ./project2/test1.html
    ./test5.html
    ./he llo.html
    ./test2.html
    ./index.html
    ./test1.html
    ./test7.html
    

    xargs使用教程 Linux下查看壓縮文件內容的 10 種方法

    使用 tar -tf 命令可以在不提取 tar 文件的情況下查看壓縮包內容。

  5. (進階)編寫一個命令或腳本遞歸的查找文件夾中最近使用的文件。更通用的做法,你可以按照最近的使用時間列出文件嗎?

    [lighthouse@VM-8-17-centos question4]$ find . -type f -mmin -120 | xargs -d '\n'  ls -tl | head -3
    -rw-rw-r-- 1 lighthouse lighthouse 10240 Jan  3 22:01 ./html.zip
    -rw-rw-r-- 1 lighthouse lighthouse     0 Jan  3 21:36 ./he llo.html
    -rw-rw-r-- 1 lighthouse lighthouse     0 Jan  3 21:15 ./project1/test1.html
    

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、MyBatis簡介 1.1、什麼是MyBatis MyBatis 是一款優秀的持久層框架 MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集的過程 MyBatis 可以使用簡單的 XML 或註解來配置和映射原生信息,將介面和 Java 的 實體類 【Plain Old ...
  • 最近網上看到了電子郵箱的新利用方法如題,下載了幾個此類軟體,發現好幾個不是不好用,就是功能不全。上博客園搜了一下,那麼可以看到有使用java和python實現的,這裡我們用Windows的批處理實現。 我們要實現的最基礎的功能,自然是執行cmd命令,有了這個其他都好說。 Windows批處理的優點: ...
  • 在 C++ 中為了操作簡潔引入了函數模板。所謂的函數模板實際上是建立一個通用函數,其函數類型或形參類型不具體指定,用一個虛擬的類型來表達,這個通用函數就稱為函數模板。 1、通用的寫法 函數模板不是一個具體的函數,編譯器不能為其生成可執行代碼。定義函數模板後只是一個對函數功能框架的描述,當它具體執行時 ...
  • 力扣104 求二叉樹的最大深度 題目: 給定一個二叉樹,找出其最大深度。 二叉樹的深度為根節點到最遠葉子節點的最長路徑上的節點數。 說明: 葉子節點是指沒有子節點的節點。 示例 給定二叉樹 [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回它的最大深度 3 ...
  • 前言: 前面的四個章節我們主要講解了MongoDB的相關基礎知識,接下來我們就開始進入使用.NET7操作MongoDB開發一個ToDoList系統實戰教程。本章節主要介紹的是如何快熟搭建一個簡單明瞭的後端項目框架。 MongoDB從入門到實戰的相關教程 MongoDB從入門到實戰之MongoDB簡介 ...
  • 在實際業務中,當後臺數據發生變化,客戶端能夠實時的收到通知,而不是由用戶主動的進行頁面刷新才能查看,這將是一個非常人性化的設計。有沒有那麼一種場景,後臺數據明明已經發生變化了,前臺卻因為沒有及時刷新,而導致頁面顯示的數據與實際存在差異,從而造成錯誤的判斷。那麼如何才能在後臺數據變更時及時通知客戶端呢... ...
  • 一、獲取微信支付碼url (1)獲取微信支付碼url主方法 /// <summary> /// 獲取微信支付二維碼 /// </summary> /// <param name="log">日誌</param> /// <param name="orderId">訂單編號</param> /// < ...
  • 目前項目當中存有 .NET Framework 和 .NET Core 兩種類型的項目,但是都需要進行容器化將其分別部署在 Windows 集群和 Linux 集群當中。在 WCF 進行容器化的時候,遇到了以下幾個問題: 1. 某些服務使用到了 WSHttpBinding 保護服務安全,要在容器里... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...