Linux系統介紹(三)shell基礎

Linux系統介紹(三)shell基礎http://blog.csdn.net/Walkerhau/article/details/78583300

概述

首先,咱們來了解一下,什麼是Shell。操作系統內核給我們提供了各種接口,同時也提供了各種用戶層的庫,理論上我們基於這些可以編寫程序實現各種我們想要的功能,不過問題是,咱們不可能做什麼事情都要重新編寫程序,這樣使用起來也太困難了。因此,操作系統(包括Linux)通常都會引入一個Shell這樣的特殊程序,這個程序會接受輸入的命令然後執行,並可能將執行結果呈現出來。總結來說,Shell是一個從輸入設備或者文件讀取命令,並且解釋、執行的用戶態程序。

在Linux系統中,通常使用的Shell程序包括有: 
* Sh (Bourne Shell) 
* Bash (Bourne Again Shell) 
* Csh (C Shell) 
* Ksh (Korn Shell)

一般來說,Bash應該是使用最多的Shell程序了,本文也主要基於Bash來展開。

Shell展開(Shell Expansion)

Shell程序是一個命令解釋器,因此在終端輸入命令之後,Shell將掃描命令並做適當的修改,這個過程稱爲Shell展開。Shell展開是Shell解釋執行之前極爲重要的一步,瞭解它將有利於你對Shell命令或者腳本的理解,本章節將逐步帶大家來了解這個過程。

命令參數解析

這裏的空格包括了製表符(Tab)。當Shell程序掃描輸入的命令時,會以連續的空格爲界,將命令切分成一組參數,因此你輸入多個空格爲界跟輸入一個空格的效果是一樣的。通常來講,第一個參數就是要執行的命令,而後面的參數則是改命令的參數。一下幾個命令其實是等效的:

# echo Hello World
Hello World
# echo   Hello World
Hello World
#    echo Hello World
Hello World
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

引號

當然,有時候你需要在一個參數中包括空格,這樣的話你就需要將這個參數以引號引起來,引號包括了單引號'跟雙引號",兩者都可以。shell會將引號中的字符串視爲一個參數,不論裏面有沒有空格。當然,特別指出的是,不要用反引號`,反引號將在後面詳細講述。

如命令echo 'Hello World!'shell解析之後會有兩個參數,分別爲echoHello World!。而如果不用引號echo Hello World!,則將解析爲三個參數。

特別提一下,對於echo命令,如果需要輸出需要轉義的字符,如回車等,則需要執行echo -e "Hello World!\n",如果不加-e,則\n會被直接顯示出來。

“` 
# echo “hello\n” 
hello\n 
# echo -e “hello\n” 
hello

“`

命令

對於shell來說,命令有內部命令(Builtin Commands)跟外部命令(External Commands)之分,所謂內部命令指的是包含在shell解析器中的命令。內部命令一般有4種類型

  • sh內部命令

    這些內部命令來源於Bourne Shell,通常包括了以下命令: 
    : . break cd continue eval exec exit export getopts hash pwd readonly return shift test/[ times trap umask unset

  • bash內部命令

    這些內部命令來源於Bourne Again Shell,通常包括了以下命令: 
    alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias

  • 修改shell行爲的內部命令

    這些內部命令用來修改shell的默認行爲。包括了set shopt命令。

  • 特殊內部命令

    由於歷史原因,POSIX標準將一些內部命令劃分爲特殊內部命令,特殊的之處在於這些命令的查找時間以及命令運行後的狀態等方面,只有當Bash以POSIX模式運行時,這些命令纔是特殊命令,否則它們跟其它內部命令沒啥區別。特殊內部命令包括了break : . continue eval exec exit export readonly return set shift trap unset

內部命令可能會被提前至於內存中,因此運行起來會比外部命令要快。對於外部命令,可以認爲除了內部命令之後就可以認爲是外部命令了,通常來講,/bin/sbin下的都是外部命令,當然,應用有關的通常也是外部命令。

我們可以通過type命令來查看一個命令是否是內部命令:

# type cd
cd is a shell builtin
# type awk
awk is /usr/bin/awk
  • 1
  • 2
  • 3
  • 4

另外,對於很多內部命令,它們可能對應的會有外部命令版本,可以通過type命令來查看:

# type -a echo
echo is a shell builtin
echo is /usr/bin/echo
# type -a cd
cd is a shell builtin
cd is /usr/bin/cd
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

反過來,我們一般可以通過命令which來查詢一個命令是否是外部命令:

# which awk
/usr/bin/awk
# which .
/usr/bin/which: no . in (/opt/rh/rh-python34/root/usr/bin:/usr/java/default/bin/:/usr/local/git/bin:/opt/ActiveTcl-8.5/bin:/root/perl5/bin:/root/env/maven/apache-maven-3.3.3/bin:/root/soft/wrk/wrk-4.0.1:/root/usr/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin)
  • 1
  • 2
  • 3
  • 4

總結一下,通過which查詢出來的是其外部命令版本,通過type默認查詢出來的是內部命令:

# which echo
/usr/bin/echo
# type echo
echo is a shell builtin
  • 1
  • 2
  • 3
  • 4

對於內部命令的詳細說明,可以查看GNU的文檔

別名

可以用alias命令給一個命令取一個別名:

# alias print=echo
# print "hello"
hello
# type print
print is aliased to `echo'
  • 1
  • 2
  • 3
  • 4
  • 5

別名一個常用的用法是用來縮寫已知的命令:

# type ls
ls is aliased to `ls --color=auto'
  • 1
  • 2

可見ls命令實際上是命令ls --color=auto的別名,這樣就相當於改變了ls命令的默認行爲了。

前面咱們通過type命令來查看命令的別名,實際上更加推薦採用alias或者which來查看:

# alias ls
alias ls='ls --color=auto'
# which ls
alias ls='ls --color=auto'
    /usr/bin/ls
  • 1
  • 2
  • 3
  • 4
  • 5

如果要取消別名,則可以採用unalias命令:

# which ls
alias ls='ls --color=auto'
    /usr/bin/ls
# unalias ls
# which ls
/usr/bin/ls
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

顯示shell展開的結果

由於shell展開的存在,你輸入的命令被展開之後可能會發生變化,如果需要知道shell展開之後的命令,可以使用內部命令set來修改shell的默認參數來顯示:

# set -x
++ printf '\033]0;%s@%s:%s\007' root traffic-base1 '~'
# echo hello         world
+ echo hello world
hello world
++ printf '\033]0;%s@%s:%s\007' root traffic-base1 '~'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

其中,以+開頭的就是展開之後的命令,可見展開之後,shell將多餘的空格去掉了。如果不要再顯示了,可以輸入命令set +x

shell控制操作符 (Control Operators)

$?操作符

每個命令執行完後都會有個退出碼(Exit Code),其值爲0時表示命令成功,否則命令失敗。這個退出碼可以通過$?來訪問,執行完命令後立馬訪問$?可以獲取該命令的退出碼,並以此來判斷命令是否成功。每個命令的執行都會產生新的退出碼,所以請務必在命令執行完,立刻訪問$?來獲取退出碼。

初看起來,$?似乎是一個shell變量,但實際上並非如此,因爲你無法對$?賦值。$?準確來說是shell的一個內部參數。

分號;

shell命令輸入時,你可以將多個命令輸入在一行,只要在不同命令之間以分號;隔開,當然分號不能是在引號中。

必須注意的是,如果將多個命令以;連接在一起,執行的結果通過$?查詢出來將只是最後一個命令的結果

&符號

通常情況下,shell會在前臺執行命令,並等待命令結束才返回。如果需要將命令放到後臺去執行,可以使用&符號放在命令最後面,這樣的話命令會被放在後臺執行,shell會立刻返回而不用等待命令結束。

注意的是,即便放在後臺執行,但是如果不處理好命令的輸入,則命令的輸出可能會繼續在當前的終端輸出,後面會講述如何處理命令的輸出。

&&操作符

此操作符表示邏輯與,你可以將兩個命令用此操作符連接起來,如cmd1 && cmd2,只有當cmd1執行成功之後,cmd2纔會被執行。這裏的成功指的是cmd1的退出碼是0。

# hello && echo world
-bash: hello: command not found
# echo hello && echo world
hello
world
  • 1
  • 2
  • 3
  • 4
  • 5

當然,&&也可以將多個命令連接起來,其執行類似,只有當前面的命令成功,後面的纔會執行。因此,將多個命令寫在一行用&&可以實現,只不過&&必須按照邏輯與的關係執行,而;號的話會執行所有的命令。

||操作符

很顯然,與&&相對,||操作符表示邏輯或的關係,同樣可以連接兩個命令,如cmd1 || cmd2,只有當cmd1失敗了,纔會執行cmd2,這裏的失敗指的是cmd1的退出碼非0。

&&||混合

這兩個操作符是可以混合使用的,其遵循的原則保持一致,且是從左向右依次判斷,結合這兩種操作符,可以實現類似於if then else的邏輯結構。如cmd1 && cmd2 || cmd3意思就是如果cmd1成功,則執行cmd2,否則執行cmd3。但務必注意的是,此處並非真正意思上的if then else邏輯,因爲如果cmd2也執行失敗,cmd3其實也會被執行。如下例:

# echo hello && echo ok || echo world
hello
ok
# echo hello && rm dfsdf || echo world
hello
rm: cannot remove ‘dfsdf’: No such file or directory
world
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

&&相當於將兩條命令邏輯上連成了一條命令,這樣就變成了cmd1-2 || cmd3,其中cmd1-2就是cmd1 && cmd2,因此,cmd3只要在cmd1-2失敗的情況下都會被執行,而cmd1-2失敗的情況有兩種,一種是cmd1失敗,一種是cmd1成功但是cmd2失敗。同樣的,||也會將兩條命令連成一條命令,如cmd1-2 || cmd3 && cmd4就相當於cmd1-2_3 && cmd4cmd4是否會執行,決定於cmd1-2_3是否失敗,以具體例子說明:

# echo hello && echo ok || echo world && rm dsdfsf || echo end
hello
ok
rm: cannot remove ‘dsdfsf’: No such file or directory
end
  • 1
  • 2
  • 3
  • 4
  • 5

這行命令相當於cmd1 && cmd2 || cmd3 && cmd4 || cmd5,可以看出cmd1cmd2cmd4還是有cmd5被執行了,而cmd3沒有執行。咱們來解析一下,爲何是如此的執行結果。首先,shell從左往右掃描執行:

  • 發現cmd1 && cmd2,由&&連成一個命令cmd1-2,因爲兩個命令都是成功的,所以都被執行了,這樣可以認爲cmd1-2成功
  • 執行成功之後,接下來是||操作符,這裏並不會因爲前面的命令是成功的,而不再執行後面所有的命令,而是||操作符相當於將cmd1-2cmd3連接成了cmd1-2_3,因爲cmd1-2成功了,所以cmd3不再執行,但是cmd1-2_3相當於執行成功了
  • 繼續執行,發現是&&操作符,同樣將cmd1-2_3cmd4連接起來,記爲cmd1-2_3-4,因爲cmd1-2_3執行成功了,所以cmd4也被執行,但是cmd4執行失敗了,所以cmd1-2_3-4相當於執行失敗
  • 繼續執行,發現是||操作符,同樣將cmd1-2_3-4cmd5連成cmd1-2_3-4_5,因爲cmd1-2_3-4執行失敗,所以cmd5被執行

可見,shell永遠都是從左往右掃描執行,&&||會將前後兩個命令連接起來,根據兩種操作符的規則就可以知道多個連起來的命令是如何執行的了。

#符號

跟其它很多語言一樣,#shell裏面用來註釋。

\轉義符號

\符號可以用來轉義一些特殊符號,如$#等。

特別指出的是,如果轉義符號放在行末單獨使用,則用來連接下一行。

shell變量

基本概念

定義跟引用

shell中也可以使用變量,變量不需要像其它語言一樣需要預先申明。shell中賦值給一個不存在的變量就相當於定義了變量,如name="Mr. Hao",就定義了name變量,後續如果再對name賦值,就相當於改變改變量的值。與很多語言不同的是,shell中變量引用以$符號開頭,後面跟變量的名字。如前面的變量,引用如下echo "$name"需要注意的是,在shell中,變量名是大小寫敏感的。

shell展開中會自動展開變量的引用,即便該變量處在雙引號中。但是,如果變量引用在單引號中,shell不會對其進行解析。

# name="Mr. Hao"
# echo "$name"
Mr. Hao
# set -x
# echo '$name'
+ echo 'Mr. Hao'
$name
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

查找變量

可以使用set命令來查找所定義的變量:

# set | grep -E '^name='
name='Mr. Hao'
  • 1
  • 2

刪除變量

與很多語言不同的是,在shell中定義的變量是可以刪除的,使用unset命令刪除定義的變量。

# set | grep -E '^name='
name='Mr. Hao'
# unset name
# set | grep -E '^name='
  • 1
  • 2
  • 3
  • 4

export聲明

通常情況下,shell在執行命令的時候會爲該命令創建子進程。如果希望將當前的變量作用到子進程,則需要將變量export聲明,這種變量稱之爲環境變量,如:

# var1="hello"
# export var2="world"
# bash
# echo "var1=$var1, var2=$var2"
var1=, var2=world
  • 1
  • 2
  • 3
  • 4
  • 5

其中,bash命令開啓了一個新的shell,可見只有export聲明的變量在新的shell中才是可見的。環境變量可以通過env命令列舉出來,在後面一節會詳細講述。此外,如果需要將非export變量重新聲明爲export變量,則只需要用export重新聲明一下即可:

# var1=hello
# env | grep var1
# export var1
# env | grep var1
var1=hello
  • 1
  • 2
  • 3
  • 4
  • 5

env命令

如果需要查看當前shell中有哪些export聲明的變量,可以使用env命令,該命令會列出當前所有export聲明的變量。請注意與set命令的區別,set命令會列出所有的變量,包括哪些不是export聲明的變量。通常,我們把env命令輸出的變量稱之爲環境變量

此外,env也常用來爲子shell預先定義一些臨時變量,如:

# var1="hello"
# env var1="tmp" bash -c 'echo "$var1"'
tmp
# echo $var1
hello
  • 1
  • 2
  • 3
  • 4
  • 5

其中,用env命令定義了臨時變量var1,然後bash命令開啓了一個子shell,並在子shell中執行了echo "$var1"命令。可見,輸出了定義的臨時變量,在命令結束後,又回到之前的shell,輸出的也是之前shell中定義的值。當然,在使用env定義臨時變量的時候,爲了方便,通常我們可以省略env命令,如:

# var1="hello"
# var1="tmp" bash -c 'echo "$var1"'
tmp
# echo $var1
hello
  • 1
  • 2
  • 3
  • 4
  • 5

另外,env命令還有一種常用的用法,就是用來開啓一個乾淨的子shell,即在子shell中不繼承所有的變量,即便這些變量在之前的shell中採用export聲明,此時env命令需要加入-i的參數,如:

# export var1="hello world"
# bash -c 'echo "var1=$var1"'
var1=hello world
# env -i bash -c 'echo "var1=$var1"'
var1=
  • 1
  • 2
  • 3
  • 4
  • 5

可見,使用env -i之後,即便var1export聲明,但是在子shell中也沒有被繼承。

變量解釋

在前面章節,我們知道shell採用$符號引用變量,在$符號後緊跟變量的名字。而shell在提取變量名字的時候一般以非字母數字(non-alphanumeric)爲邊界,這有時候就會產生問題,如:

# prefix=Super
# echo Hello $prefixman and $prefixgirl
Hello  and
  • 1
  • 2
  • 3

可見,shell並不能提取我們定義的變量prefix,因爲其後並沒有非字母數字的字符爲界。這種情況下,我們可以使用{}將變量名保護起來。

# prefix=Super
# echo Hello ${prefix}man and ${prefix}girl
Hello Superman and Supergirl
  • 1
  • 2
  • 3

非綁定(unbound)變量

所謂非綁定(unbound)變量其實指的是沒有預先定義的變量,或者說不存在的變量。默認情況下,shell在解釋這種變量的時候會以空字符串替代:

# echo $unbound_var
  • 1
  • 2

如果需要shell在這種情況下報錯,可以配置shell選項set -o nounset,或者簡寫爲set -u

# echo $unbound_var
bash: unbound_var: unbound variable
# set +u
# echo $unbound_var
  • 1
  • 2
  • 3
  • 4
  • 5

當然,由例子中可以看到,要取消該配置,可以相應的設置set +o nounset,或者簡寫爲set +u

特殊變量

shell中預定義了很多特殊的變量,這一節咱們來說一下常見的幾個變量。

$PS1變量

shell終端輸入命令時,咱們總是可以看到在輸入行首總是會有提示符,如:

mrhao:~$
  • 1

其中,mrhao:~$就是提示符,這個字串實際上是由shell變量$PS1決定的。如果咱們改變一下該變量的值,提示符也會相應的改變:

mrhao:~$ PS1="hello > "
hello > echo "PS1 value is '$PS1'"
PS1 value is 'hello > '
hello >
  • 1
  • 2
  • 3
  • 4

爲了方便在提示符中顯示系統的某些實時信息,$PS1變量定義了一些特殊的字符:

字符 說明
\w 表示工作目錄
\u 表示用戶名
\h 表示系統的hostname

當然,這裏只列舉了幾個,詳細的可以查看Linux手冊。另外,$PS1中還可以對對其中不同部分採用不同顏色顯示,如:

# RED='\[\033[01;31m\]'
# WHITE='\[\033[01;00m\]'
# GREEN='\[\033[01;32m\]'
# BLUE='\[\033[01;34m\]'
# PS1="$GREEN\u$WHITE@$BLUE\h$WHITE\w\$ "
mrhao@mrhao-host~$ echo "$PS1"
\[\033[01;32m\]\u\[\033[01;00m\]@\[\033[01;34m\]\h\[\033[01;00m\]\w$
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

$PATH變量

當我們在Linux的terminal裏面輸入命令的時候,shell需要在一系列的目錄中查找輸入的命令,如果沒有查找到會直接報command not found的錯誤。而這些查找的目錄就定義在$PATH變量中。

# echo $PATH
/opt/rh/rh-python34/root/usr/bin:/usr/java/default/bin/:/usr/local/git/bin:/opt/ActiveTcl-8.5/bin:/root/perl5/bin:/root/env/maven/apache-maven-3.3.3/bin:/root/soft/wrk/wrk-4.0.1:/root/usr/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
  • 1
  • 2

其中,每個目錄以:隔開,如果需要增加目錄,可以:

# PATH=$PATH:/opt/local/bin
# echo $PATH
/opt/rh/rh-python34/root/usr/bin:/usr/java/default/bin/:/usr/local/git/bin:/opt/ActiveTcl-8.5/bin:/root/perl5/bin:/root/env/maven/apache-maven-3.3.3/bin:/root/soft/wrk/wrk-4.0.1:/root/usr/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/opt/local/bin
  • 1
  • 2
  • 3

加入新的路徑的時候請務必帶上之前的路徑,$PATH:<new path>否則,很多默認的系統路徑將被覆蓋,導致很多命令失效。

特別注意的是,$PATH變量中目錄的順序是很重要的,如果shell在前面的目錄中找到了命令,則不會查找後面的目錄。如果你想把某個重名的命令優先執行,就需要把它對應的目錄放在$PATH的前面。

網絡代理變量

在Linux系統中,很多時候我們需要訪問外部網絡,比如使用curl命令下載文件等等。而有的時候,訪問訪問外部網絡咱們需要設置代理,在Linux系統中,使用網絡代理非常簡單,只要配置幾個變量即可:

變量 說明
http_proxy 設置訪問http請求所需要的代理,如http_proxy=http://10.10.10.100:80
https_proxy 設置訪問https請求所需要的代理,如https_proxy=http://10.10.10.100:80
ftp_proxy 設置訪問ftp請求所需要的代理,如ftp_proxy=http://10.10.10.100:80
no_proxy 設置哪些域名或者IP不需要走代理,如no_proxy=localhost,127.0.0.1

$PWD變量

PWD變量是一個由shell自動設置的變量,其值表示當前目錄的絕對路徑,與命令pwd輸出相同。

shell嵌入與shell選項

shell嵌入(shell embedding)

shell可以嵌入在同一個命令行中,也就是shell在掃描解釋命令行的時候,可能會從當前的shell進程中fork出一個新的shell進程,並將有關命令放在新進程中運行。如下例:

# var1=hello
# echo $(var1=world; echo $var1)
world
# echo $var1
hello
  • 1
  • 2
  • 3
  • 4
  • 5

如其中$()便開啓了一個新的shell進程,或者成爲子shell,並在此shell中運行命令var1=world; echo $var1,此時輸出的是子shell中定義的var1。當命令結束後,子shell進程退出,並將輸出的結果world返回給之前的shell(或者父shell)的echo命令,父shell最後輸出world。而且,在子shell中定義相同的var1變量並不會改變父shell中的變量。

特別注意的是,因爲子shellfork出來的進程,根據Linux進程fork的特點,子進程將共享父進程的數據空間,而只在寫的時候拷貝新的數據空間,因此,創建出來的子shell是會繼承所有父shell的變量,不論該變量是否被export聲明

# var1=hello
# var2="$(echo $var1 world)"
# echo $var2
hello world
  • 1
  • 2
  • 3
  • 4

可見,雖然var1變量沒有export聲明,但是在子shell中還是可見的。這點與使用bash -c開啓的shell是不同的。

$()可以將子shell嵌入到命令行中,當然,$()是可以嵌套使用的,這樣可以用來在子shell中開啓它的子shell

# A=shell
# echo $C$B$A $(B=sub;echo $C$B$A; echo $(C=sub;echo $C$B$A))
shell subshell subsubshell
  • 1
  • 2
  • 3

反引號(backticks)

在上面我們可以通過$()將子shell嵌入命令行中,爲了方便,我們同樣可以用反引號`將子shell嵌入。

# var1=hello
# echo `var1=world; echo $var1`
world
# echo $var1
hello
  • 1
  • 2
  • 3
  • 4
  • 5

但是,使用反引號不能夠嵌套子shell,因此如果需要嵌套子shell時,只能使用$()

反引號跟單引號是本質的不同的,單引號與雙引號一樣,用來將連續的字串作爲整體引起來,只不過單引號中將不執行變量的引用解析,而反引號則是嵌入子shell

shell選項

其實在前面咱們已經使用了不少shell的選項,如set -u在變量不存在是報錯,set -xshell展開的結果顯示出來等。此外,可以才用echo $-將當期設置的shell選項打印出來。

shell歷史記錄

shell中執行命令的時候,shell會將最近的命令使用歷史記錄下來,這樣你可以很方便的查看最近做了什麼操作。

查看歷史記錄

命令history可以用來查看shell的歷史記錄,裏面記錄了你最近輸入的所有命令。當然,很多時候你更加關心最近的幾個命令,你可以使用history 10來顯示最近的10個命令。另外,shell通常還會將最近的歷史記錄寫在~/.bash_history文件中,因此查看該文件同樣可以查看歷史記錄。

執行歷史的命令

shell提供了很多高級用法使得你可以很方便的執行以前執行過的命令。

首先,咱們先顯示一下過去的10個命令,可以看到每個命令前面都有其對應的序號。

# history 10
 1000  history
 1001  history 10
 1002  echo "hello world"
 1003  ls -l
 1004  ps -ef | grep named
 1005  env | grep http
 1006  grep hello /var/log/messages
 1007  tmux ls
 1008  find . -name "hello"
 1009  history 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

下面列舉比較常用的shell重複執行歷史記錄中命令的方法:

命令 說明
!! shell中輸入兩個感嘆號會執行上一個命令
!keyword 輸入一個感嘆號後跟關鍵字,會搜索歷史記錄中最先以該關鍵字開始的命令。如!find會執行序號爲1008的命令。
!n 其中n代表歷史記錄中的序號,表示執行序號爲n的命令。

另外,對於!keyword的用法,還有一個高級功能,你可以將符合該條件的命令進行改造後執行,如:

# echo "test1"
test1
# !ec:s/1/2/
echo "test2"
test2
  • 1
  • 2
  • 3
  • 4
  • 5

其中,:s/1/2/將命令echo "test1"替換成echo "test2"然後執行了。

搜索歷史記錄

shell終端中按Ctrl-r會打開shell的搜索模式,在改模式下輸入關鍵字會顯示最近包含改關鍵字的命令,再按一下Ctrl-r會繼續顯示前面一條符合條件的命令,找到你需要的命令後回車就可以執行改命令了。

修改歷史記錄的有關配置

有多個配置可以用來改變歷史記錄的有關信息,通常都是通過有關環境變量來配置:

環境變量 說明
$HISTSIZE 這個變量用來配置shell應該保持多少行的歷史記錄,在很多發行版本中,默認值一般爲500或者1000
$HISTFILE 這個變量用來配置歷史記錄文件存放的位置,通常來講,默認路徑爲~/.bash_history
$HISTFILESIZE 這個變量用來配置歷史記錄文件可以存放多少行的歷史記錄

阻止記錄某些命令

在有些時候,我們並不想把某些命令記錄在歷史記錄中,比如有的命令裏面包括了敏感信息如密碼等。在新版本的shell中,通常我們可以在輸入的命令前面加入空格,這樣shell就不會記錄這樣的命令,當然,如果你的發行版本默認並不支持,你可以配置環境變量來打開這個功能:

export HISTIGNORE="[ \t]*"
  • 1

例如:

# history 5
 1023  ls -l
 1024  echo ""
 1025  history 5
 1026  ls
 1027  history 5
#  echo "password=123456"
password=123456
# history 5
 1025  history 5
 1026  ls
 1027  history 5
 1028   echo "password=123456"
 1029  history 5
# export HISTIGNORE="[ \t]*"
# history 5
 1027  history 5
 1028   echo "password=123456"
 1029  history 5
 1030  export HISTIGNORE="[ \t]*"
 1031  history 5
#  echo "password=123456"
password=123456
# history 5
 1027  history 5
 1028   echo "password=123456"
 1029  history 5
 1030  export HISTIGNORE="[ \t]*"
 1031  history 5
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

可見,在設置$HISTIGNORE變量之後,在前面加了空格的命令將不再記錄。這在保護敏感信息的時候非常有用。

文件匹配(File Globbing)

文件匹配(File Globbing)又成爲動態文件名生成,用它可以非常方便的在shell中輸入文件名。

*星號

*星號在shell中用來匹配任意數量的字符,比如文件名File*.mp4,將匹配以File開頭,.mp4結尾的任何文件名。shell在掃描解釋命令的時候會自動去查找符合該匹配的所有文件或目錄。當然,你也可以只用*來匹配所有的文件及目錄,但請注意,只使用*跟不帶*還是有所區別的,

# ls
definition.yaml  example  __init__.py  tags.yaml  test.py  test_sample.html  test_sample.py
# ls *
definition.yaml  __init__.py  tags.yaml  test.py  test_sample.html  test_sample.py

example:
testcase
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

可見,帶上*後不僅把當前目錄的所有文件及目錄顯示出來,而且還把目錄下的內容顯示出來了。

?問號

問號用來匹配一個字符,如File?.mp4可以匹配File1.mp4

[]方括號

[]方括號也用來匹配一個字符,但是在括號裏面可以指定一個字符集用來限定匹配的字符必須在該字符集內,字符集裏面的字符順序沒有關係。

# ls
file1  file2  file3  File4  File55  FileA  fileab  Fileab  FileAB  fileabc
# ls File[5A]
FileA
# ls File[A5]
FileA
# ls File[A5][5b]
File55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

如果需要匹配不在某個字符集裏面的字符,可以在[]第一個字符加入!

# ls file[!5]*
file1  file2  file3  fileab  fileabc
  • 1
  • 2

特別的,爲了方便,[]中可以使用-來定義一些連續的字符集(Range匹配),常用的這類字符集包括:

字符集 說明
0-9 表示數字字符集
a-z 表示小寫字母字符集
A-Z 表示大寫字母字符集

當然,你也不必要把所有範圍都包括在內,如[a-d]可以用來限定從ad的小寫字母集。另外,用-連起來的字符集還可以跟其它字符集一起使用,如[a-d_]表示ad的小寫字母加上_所組成的字符集。

  • Range匹配的大小寫問題

    對於[]的Range匹配,還有一點很重要。在很多發行版本中,默認情況下,[]的Range匹配是忽略大小寫的

    
    # ls
    
    Test1  test2
    
    # ls [a-z]*
    
    Test1  test2
    
    # ls [A-Z]*
    
    Test1  test2
    
    # ls [t]*
    
    test2
    
    # ls [T]*
    
    Test1
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注意,是[]的Range匹配會忽略大小寫,而如果不是Range匹配還是大小寫敏感的:

    
    # ls
    
    Test1  test2
    
    # ls [T]*
    
    Test1
    
    # ls [t]*
    
    test2
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果需要大小寫敏感,可以設置環境變量LC_ALL

    
    # LC_ALL=C
    
    
    # ls [a-z]*
    
    test2
    
    # ls [A-Z]*
    
    Test1
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    當然,請務必注意,LC_ALL的會改變當前的語言環境,還請慎重使用,建議只在臨時的子shell中使用。

阻止文件匹配(File Globbing)

有時候我們就是需要輸出*等匹配符號,這個時候就需要阻止shell做相應的匹配。可以使用轉義符號\來做到這點,或者將匹配符號放在引號中:

# echo *
Test1 test2
# echo \*
*
# echo '*'
*
# echo "*"
*
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

本博文還可以在博主個人主頁中找到。


發佈了7 篇原創文章 · 獲贊 139 · 訪問量 38萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章