Linux系統介紹(三)shell基礎

概述

首先,咱們來了解一下,什么是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

引號

當然,有時候你需要在一個參數中包括空格,這樣的話你就需要將這個參數以引號引起來,引號包括了單引號'跟雙引號",兩者都可以。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

另外,對于很多內部命令,它們可能對應的會有外部命令版本,可以通過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

反過來,我們一般可以通過命令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)

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

# which echo
/usr/bin/echo
# type echo
echo is a shell builtin

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

別名

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

# alias print=echo
# print "hello"
hello
# type print
print is aliased to `echo'

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

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

可見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

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

# which ls
alias ls='ls --color=auto'
    /usr/bin/ls
# unalias ls
# which ls
/usr/bin/ls

顯示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 '~'

其中,以+開頭的就是展開之后的命令,可見展開之后,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

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

||操作符

很顯然,與&&相對,||操作符表示邏輯或的關系,同樣可以連接兩個命令,如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

&&相當于將兩條命令邏輯上連成了一條命令,這樣就變成了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

這行命令相當于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

查找變量

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

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

刪除變量

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

# set | grep -E '^name='
name='Mr. Hao'
# unset name
# set | grep -E '^name='

export聲明

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

# var1="hello"
# export var2="world"
# bash
# echo "var1=$var1, var2=$var2"
var1=, var2=world

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

# var1=hello
# env | grep var1
# export var1
# env | grep var1
var1=hello

env命令

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

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

# var1="hello"
# env var1="tmp" bash -c 'echo "$var1"'
tmp
# echo $var1
hello

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

# var1="hello"
# var1="tmp" bash -c 'echo "$var1"'
tmp
# echo $var1
hello

另外,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=

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

變量解釋

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

# prefix=Super
# echo Hello $prefixman and $prefixgirl
Hello  and

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

# prefix=Super
# echo Hello ${prefix}man and ${prefix}girl
Hello Superman and Supergirl

非綁定(unbound)變量

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

# echo $unbound_var

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

# echo $unbound_var
bash: unbound_var: unbound variable
# set +u
# echo $unbound_var

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

特殊變量

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

$PS1變量

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

mrhao:~$

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

mrhao:~$ PS1="hello > "
hello > echo "PS1 value is '$PS1'"
PS1 value is 'hello > '
hello >

為了方便在提示符中顯示系統的某些實時信息,$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$

$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

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

# 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

加入新的路徑的時候請務必帶上之前的路徑,$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

如其中$()便開啟了一個新的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

可見,雖然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

反引號(backticks)

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

# var1=hello
# echo `var1=world; echo $var1`
world
# echo $var1
hello

但是,使用反引號不能夠嵌套子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

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

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

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

# echo "test1"
test1
# !ec:s/1/2/
echo "test2"
test2

其中,: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]*"

例如:

# 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

可見,在設置$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

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

?問號

問號用來匹配一個字符,如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

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

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

特別的,為了方便,[]中可以使用-來定義一些連續的字符集(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
    

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

    # ls
    Test1  test2
    # ls [T]*
    Test1
    # ls [t]*
    test2
    

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

    # LC_ALL=C
    # ls [a-z]*
    test2
    # ls [A-Z]*
    Test1
    

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

阻止文件匹配(File Globbing)

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

# echo *
Test1 test2
# echo \*
*
# echo '*'
*
# echo "*"
*

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容