日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Zsh vs. Bash不完全对比解析,zsh是一种更强大的被成为“终极”的Shell

發布時間:2025/6/15 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Zsh vs. Bash不完全对比解析,zsh是一种更强大的被成为“终极”的Shell 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

https://www.zhihu.com/question/21418449

?

?

Mort | Zsh vs. Bash:不完全對比解析(1)

2014-10-07 ?bdpqlxz

Zsh和Bash,究竟有何不同

已經有不少人寫過類似“為什么Zsh比Bash好”“為什么Zsh比* shell好”的文章了,講解如何配置Zsh或折騰各種oh-my-zsh主題的教程也是一搜一大籮,但是卻極少看到Zsh和Bash這兩個Shell作為腳本語言時的具體差異比較。那么,這里就是一篇,從語言特性的角度上簡單整理了兩者一些細微的不兼容之處,供編寫可移植Shell腳本時參考。(僅僅是從我自己過去的經驗教訓中總結出來的,所以應該也是不完全的。)

開始之前:理解Zsh的仿真模式(emulation mode)

一種流行的說法是,Zsh是與Bash兼容的。這種說法既對,也不對,因為Zsh本身作為一種腳本語言,是與Bash不兼容的。符合Bash規范的腳本無法保證被Zsh解釋器正確執行。但是,Zsh實現中包含了一個屌炸天的仿真模式(emulation mode),支持對兩種主流的Bourne衍生版shell(bash、ksh)和C shell的仿真(csh的支持并不完整)。在Bash的仿真模式下,可以使用與Bash相同的語法和命令集合,從而達到近乎完全兼容的目的。為了激活對Bash的仿真,需要顯式執行:

$ emulate bash

等效于:

$ emulate sh

Zsh是不會根據文件開頭的shebang(如#!/bin/sh和#!/bin/bash)自動采取兼容模式來解釋腳本的,因此,要讓Zsh解釋執行一個其他Shell的腳本,你仍然必須手動emulate sh或者emulate ksh,告訴Zsh對何種Shell進行仿真。

那么,Zsh究竟在何時能夠自動仿真某種Shell呢?

對于如今的絕大部分GNU/Linux(Debian系除外)和Mac OS X用戶來說,系統默認的/bin/sh指向的是bash:

$ file /bin/sh /bin/sh: symbolic link to `bash'

不妨試試用zsh來取代bash作為系統的/bin/sh:

# ln -sf /bin/zsh /bin/sh

所有的Bash腳本仍然能夠正確執行,因為Zsh在作為/bin/sh存在時,能夠自動采取其相應的兼容模式(emulate sh)來執行命令。也許正是因為這個理由,Grml直接選擇了Zsh作為它的/bin/sh,對現有的Bash腳本能做到近乎完美的兼容。

無關主題:關于/bin/sh和shebang的可移植性

說到/bin/sh,就不得不提一下,在Zsh的語境下,sh指的是大多數GNU/Linux發行版上/bin/sh默認指向的bash,或者至少是一個Bash的子集(若并非全部GNU Bash的最新特性都被實現的話),而非指POSIX shell。因此,Zsh中的emulate sh可以被用來對Bash腳本進行仿真。

眾所周知,Debian的默認/bin/sh是 dash(Debian Almquist shell),這是一個純粹POSIX shell兼容的實現,基本上你要的bash和ksh里的那些高級特性它都沒有。“如果你在一個#!/bin/sh腳本中用到了非POSIX shell的東西,說明你的腳本寫得是錯的,不關我們發行版的事情。”Debian開發者們在把默認的/bin/sh換成dash,導致一些腳本出錯時這樣宣稱道。當然,我們應該繼續假裝與POSIX shell標準保持兼容是一件重要的事情,即使現在大家都已經用上了更高級的shell。

因為有非GNU的Unix,和Debian GNU/Linux這類發行版的存在,你不能夠假設系統的/bin/sh總是GNU Bash,也不應該把#!/bin/sh用作一個Bash腳本的shebang(——除非你愿意放棄你手頭Shell的高級特性,寫只與POSIX shell兼容的腳本)。如果想要這個腳本能夠被方便地移植的話,應指定其依賴的具體Shell解釋器:

#!/usr/bin/env bash

這樣系統才能夠總是使用正確的Shell來運行腳本。

(當然,顯式地調用bash命令來執行腳本,shebang怎樣寫就無所謂了)


echo命令 / 字符串轉義

Zsh比之于Bash,可能最容易被注意到的一點不同是,Zsh中的echo和printf是內置的命令。

$ which echo echo: shell built-in command$ which printf printf: shell built-in command

Bash中的echo和printf同樣是內置命令:

$ type echo echo is a shell builtin$ type printf echo is a shell builtin

感謝讀者提醒,在Bash中不能通過which來確定一個命令是否為外部命令,因為which本身并不是Bash中的內置命令。which在Zsh中是一個內置命令。

Zsh內置的echo命令,與我們以前在GNU Bash中常見的echo命令,使用方式是不兼容的。

首先,請看Bash:

$ echo \ $ echo \\\\

我們知道,因為這里傳遞給echo的只是一個字符串(允許使用反斜杠\轉義),所以不加引號與加上雙引號是等價的。Bash輸出了我們預想中的結果:每兩個連續的\轉義成一個\字符輸出,最終2個變1個,4個變2個。沒有任何驚奇之處。

你能猜到Zsh的輸出結果么?










$ echo \ $ echo \\\

(゜Д゜*)

解釋稍后。

我們還知道,要想避免一個字符串被反斜杠轉義,可以把它放進單引號。正如我們在Bash中所清楚看到的這樣,所有的反斜杠都照原樣輸出:

$ echo '\\' \ $ echo '\\\\' \\\

再一次,你能猜到Zsh的輸出結果么?










$ echo '\\'$ echo '\\\\' \

((((((゜Д゜*))))))))))))

這個解釋是這樣的:在前一種不加引號(或者加了雙引號)的情形下,傳遞給echo內部命令的字符串將首先被轉義,echo \\中的\\被轉義成\,echo \\\\中的\\\\被轉義成\\。然后,在echo這個內部命令輸出到終端的時候,它還要把這個東西再轉義一遍,一個單獨的\沒法轉義,所以仍然是作為\輸出;連續的\\被轉義成\,所以輸出就是\。因此,echo \\和echo \\\\的輸出相同,都是\。

為了讓Zsh中echo的輸出不被轉義,需要顯式地指明-E選項:

$ echo -E \ $ echo -E \\\\

于是,我們也就知道在后一種加單引號的情形下,如何得到與原字符串完全相同的輸出了:

$ echo -E '\\' \ $ echo -E '\\\\' \\\

而Bash的echo默認就是不對輸出進行轉義的,若要得到轉義的效果,需顯式地指定-e選項。Bash和Zsh中echo命令用法的不兼容,在這里體現出來了。

變量的自動分字(word splitting)

在Bash中,你可以通過調用外部命令echo輸出一個字符串:

echo $text

我們知道,Bash會對傳遞給命令的字符串進行分字(根據空格或換行符),然后作為多個參數傳給echo。當然,作為分隔符的換行,在最終輸出時就被抹掉了。于是,更好的習慣是把變量名放在雙引號中,把它作為一個字符串傳遞,這樣就可以保留文本中的換行符,將其原樣輸出。

echo "$text"

在Zsh中,你不需要通過雙引號來告訴解釋器“$text是一個字符串”。解釋器不會把它轉換成一個由空格或者\n分隔的參數列表或者別的什么。所以,沒有Bash中的trick,直接echo $text就可以保留換行符。但是,如前一節所說,我們需要一個多余的工作來保證輸出的是未轉義的原始文本,那就是-E選項:

echo -E $text

從這里我們看到,Zsh中的變量在傳遞給命令時是不會被自動切分成words然后以多個參數的形式存在的。它仍然保持為一個量。這是它與傳統的Bourne衍生shell(ksh、bash)的一個重要不兼容之處。這是Zsh的特性,而不是一個bug。

通配符展開(globbing)

通配符展開(globbing)也許是Unix shell中最為實用化的功能之一。比起正則表達式,它的功能相當有限,不過它的確能滿足大部分時候的需求:依據固定的前綴或后綴匹配文件。需要更復雜模式的時候其實是很少見的,至少在文件的命名和查找上。

Bash和Zsh對通配符展開的處理方式有何不同呢?舉個例子,假如我們想要列舉出當前目錄下所有的.markdown文件,但實際上又不存在這樣的文件。在Zsh中:(注意到這里使用了內置的echo,因為我們暫時還不想用到外部的系統命令)

$ echo *.markdown zsh: no matches found: *.markdown

Bash中:

$ echo *.markdown *.markdown

Zsh因為通配符展開失敗而報錯;而Bash在通配符展開失敗時,會放棄把它作為通配符展開、直接把它當做字面量返回。看起來,Zsh的處理方式更優雅,因為這樣你就可以知道這個通配符確實無法展開;而在Bash中,你很難知道究竟是不存在這樣的文件,還是存在一個文件名為'*.markdown'的文件。

接下來就是不那么和諧的方面了。

在Zsh中,用ls查看當然還是報錯:

$ ls *.markdown zsh: no matches found: *.markdown

Bash,這時候調用ls也會報錯。因為當前目錄下沒有.markdown后綴的文件,通配符展開失敗后變成字面的'*.markdown',這個文件自然也不可能存在,所以外部命令ls報錯:

$ ls *.markdown ls: cannot access *.markdown: No such file or directory

同樣是錯誤,差別在哪里?對于Zsh,這是一個語言級別的錯誤;對于Bash,這是一個外部命令執行的錯誤。這件差別很重要,因為它意味著后者可以被輕易地catch,而前者不能。

想象一個常見的命令式編程語言,Java或者Python。你可以用try...catch或類似的語言結構來捕獲運行時的異常,比較優雅地處理無法預料的錯誤。Shell當然沒有通用的異常機制,但是,你可以通過檢測某一段命令的返回值來模擬捕獲運行時的錯誤。例如,在Bash里可以這樣:

$ if ls *.markdown &>/dev/null; then :; else echo $?; fi 2

于是,在通配符展開失敗的情形下,我們也能輕易地把外部命令的錯誤輸出重定向到/dev/null,然后根據返回的錯誤碼執行后續的操作。

不過在Zsh中,這個來自Zsh解釋器自身的錯誤輸出卻無法被重定向:

$ if ls *.markdown &>/dev/null; then :; else echo $?; fi zsh: no matches found: *.markdown 1

大部分時候,我們并不想看到這些丑陋多余的錯誤輸出,我們期望程序能完全捕獲這些錯誤,然后完成它該完成的工作。但這也許是一種正常的行為。理由是,在程序語言里,syntax error一般是無法簡單地由用戶在運行階段自行catch的,這個報錯工作將直接由解釋器來完成。除非,當然,除非我們用了邪惡的eval。

$ if eval "ls *.markdown" &>/dev/null; then :; else echo $?; fi 1

Eval is evil. 但在Zsh中捕獲這樣的錯誤,似乎沒有更好的辦法了。必須這么做的原因就是:Zsh中,通配符展開失敗是一個語法錯誤。而在Bash中則不是。

基于上述理由,依賴于Bash中通配符匹配失敗而直接把"*"當作字面量傳遞給命令的寫法,在Zsh中是無法正常運行的。例如,在Bash中你可以:(雖然在大部分情況下能用,但顯然不加引號是不科學的)

$ find /usr/share/git -name *.el

因為Zsh不會在glob擴展失敗后自動把"*"當成字面量,而是直接報錯終止運行,所以在Zsh中你必須給"*.el"加上引號,來避免這種擴展:

$ find /usr/share/git -name "*.el"

字符串比較

在Bash中判斷兩個字符串是否相等:

[ "$foo" = "$bar" ]

或與之等效的(現代編程語言中更常見的==比較運算符):

[ "$foo" == "$bar" ]

注意等號左右必須加空格,變量名一定要放在雙引號中。(寫過Shell的都知道這些規則的重要性)

在條件判斷的語法上,Zsh基本和Bash相同,沒有什么改進。除了它的解釋器想得太多,以至于不小心把==當做了一個別的東西:

$ [ foo == bar ]; echo $? zsh: = not found

要想使用我們最喜歡的==,只有把它用引號給保護起來,不讓解釋器做多余的解析:

$ [ foo "==" bar ]; echo $? 1

所以,為了少打幾個字符,還是老老實實用更省事的=吧。

數組

同樣用一個簡單的例子來說明。Bash:

array=(alpha bravo charlie delta) echo $array echo ${array[*]} echo ${#array[*]} for ((i=0; i < ${#array[*]}; i++)); doecho ${array[$i]} done

輸出:

alpha alpha bravo charlie delta 4 alpha bravo charlie delta

很容易看到,Bash的數組下標是從0開始的。$array取得的實際上是數組的第一個元素的值,也就是${array[0]}(這些行為和C有點像)。要想取得整個數組的值,必須使用${array[*]}或${array[@]},因此,獲取數組的長度可以使用${#array[*]}。在Bash中,必須記得在訪問數組元素時給整個數組名連同下標加上花括號,比如,${array[*]}不能寫成$array[*],否則解釋器會首先把$array當作一個變量來處理。

再來看這段Zsh:

array=(alpha bravo charlie delta) echo $array echo $array[*] echo $#array for ((i=1; i <= $#array[*]; i++)); doecho $array[$i] done

輸出:

alpha bravo charlie delta alpha bravo charlie delta 4 alpha bravo charlie delta

在Zsh中,$array和$array[*]一樣,可以用來取得整個數組的值。因此獲取數組的長度可直接用$#array。

Zsh的默認數組下標是從1而不是0開始的,這點更像C shell。(雖然一直無法理解一個名字叫C的shell為何會采用1作為數組下標開始這種奇葩設定)

最后,Zsh不需要借助花括號來訪問數組元素,因此Bash中必需的花括號都被略去了。

關聯數組

Bash 4.0+和Zsh中都提供了對類似AWK關聯數組的支持。

declare -A array array[mort]=foo

和普通的數組一樣,在Bash中,必須顯式地借助花括號來訪問一個數組元素:

echo ${array[mort]}

而Zsh中則沒有必要:

echo $array[mort]

說到這里,我們注意到Zsh有一個不同尋常的特性:支持使用方括號進行更復雜的globbing,array[mort]這樣的寫法事實上會造成二義性:究竟是取array這個關聯數組以mort為key的元素值呢,還是以通配符展開的方式匹配當前目錄下以"array"開頭,以"m"、"o"、"r"或"t"任一字符結尾的文件名呢?

在array[mort]=作為命令開始的情況下,不存在歧義,這是一個對關聯數組的賦值操作。在前面帶有$的情況下,Zsh會自動把$array[mort]識別成取關聯數組的值,這也沒有太大問題。問題出在它存在于命令中間,卻又不帶$的情況,比如:

read -r -d '' array[mort] << 'EOF' hello world EOF

我們的本意是把這個heredoc賦值給array[mort]數組元素。在Bash中,這是完全合法的。然而,在Zsh中,解釋器會首先試圖對"array[mort]"這個模式進行glob展開,如果當前目錄下沒有符合該模式的文件,當然就會報出一個語法錯誤:

zsh: no matches found: array[mort]

這是一件很傻的事情,為了讓這段腳本能夠被Zsh解釋器正確執行,我們需要把array[mort]放在引號中以防止被展開:

read -r -d '' 'array[mort]' << 'EOF' hello world EOF

這是Zsh在擴展了一些強大功能的同時帶來的不便之處(或者說破壞了現有腳本兼容性的安全隱患,又或者是讓解釋器混亂的pitfalls)。

順便說一句,用Rake構建過項目的Rails程序員都知道,有些時候需要在命令行下通過方括號給rake傳遞參數值,如:

$ rake seeder:seed[100]

Zsh這個對方括號展開的特性確實很不方便。如果不想每次都用單引號把參數括起來,可以完全禁止Zsh對某條命令后面的參數進行glob擴展:(~/.zshrc)

alias rake="noglob rake"

嗯,對于rake命令來說,glob擴展基本是沒有用的。你可以關掉它。

分號與空語句

雖然有點無聊,但還是想提一下:Bash不允許語句塊中使用空語句,最小化的語句是一個noop命令(:);而Zsh允許空語句

剛開始寫Bash的時候,總是記不得什么時候該加分號什么時候不該加。比如

if [ 1 ] then: fi

如果放在一行里寫,應該是

if [ 1 ]; then :; fi

then后面是不能接分號的,如果寫成

if [ 1 ]; then; :; fi

就會報錯:

bash: syntax error near unexpected token `;'

解釋是:then表示一個代碼段的開始,fi表示結束,這中間的內容必須是若干行命令,或者以分號;結尾的放在同一行內的多條命令。我們知道在傳統的shell中,分號本身并不是一條命令,空字符串也不是一條命令,因此,then后面緊接著的分號就會帶來一條語法錯誤。(有些時候對某個“語言特性”的所謂解釋只是為了掩飾設計者在一開始犯的錯誤,所以就此打住)

在Zsh中,上述兩種寫法都合法。因為它允許只包含一個分號的空命令。

$ ;

當然,因為分號只是一個語句分隔符,所以沒有也是可以的。這種寫法在Zsh中合法:(then的語句塊為空)

if [ 1 ]; then fi

第二彈

其實只是先挖個坑而已。我也不知道有沒有時間寫,暫且記上。

Zsh vs. Bash:不完全對比解析(2)

  • 別名,函數定義和作用域
  • 協進程(coprocess)
  • 重定向
  • 信號和陷阱(trap)

?

?

Linux服務器上zsh和bash的對比

使用默認指令列模式(bash shell)的管理員可能想仔細看看zshell或是zsh。由于它于bash相似,功能又有所加強,zsh在Linux社區獲得了關注。那么zsh有什么不同之處呢?本文就列出在Linux服務器上zsh和bash的數據形式的對比。

作者:Mark 譯來源:TechTarget中國|2011-06-13 14:03
移動端 收藏 分享

?

使用默認指令列模式(bash shell)的管理員可能想仔細看看zshell或是zsh。由于它于bash相似,功能又有所加強,zsh在Linux社區獲得了關注。

那么zsh有什么不同之處呢?首先,zsh在感覺和功能上都和bash相似。但是一些增強功能讓zsh變成一個有趣的選擇。下面是一臺Linux服務器上zsh和bash的數據形式的對比:

Zsh增強功能:標簽完成和拼寫錯誤修正

用過bash標簽完成的管理員會發現zsh中的增加功能令人印象深刻。這些功能包括菜單中現有的自動完成命令選項,該菜單可以通過使用箭頭鍵滾動。舉例來說,鍵入以下命令將提供可能命令行標記的列表:

$ ls -

或是

$ rm -

選擇要取消的特定程序,程序列表就和取消命令一起可用了。

另一個功能在內置頁面程序中,它提供到less命令的快捷方式。要訪問它,輸入:

$<filename

這和在命令行上運行less文件名一樣。

對笨拙的打字員來說,拼寫錯誤修正功能可用了。例如,如果你輸入了一條錯誤命令,zsh會提示修正:

$ lls

zsh: 要將 'lls'修改為 'ls' [nyae]嗎?

要修改它,輸入y,命令就更正為ls,接著命令就準備運行了。

其它選項也很實用。輸入n拒絕命令修正,輸入a中斷命令,輸入e跳轉到命令行進行編輯。這個自動修正功能也能用于命令行標記和文件名,包括修改無效Git分支名稱一類的機密事務。

開始使用zsh

為了快速地開始使用zsh,可利用Robby Russell收集的zsh主題、功能和工具,它們被預先打包成“Oh My Zsh”。

$ wget --no-check-certificate https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh

手動的zsh安裝指令也可用,需要用戶克隆Git repo并復制在.zshrc的草稿模板中。

“Oh My Zsh”知識庫包含一個主題和功能集合用于現有zsh環境的建立和改變。它也能和卸載腳本一起用來簡化移除:

$ uninstall_oh_my_zsh

與zsh shell一起供給的還有一些很好的文檔和zsh參考卡。GitHub等網站上的在線資源是.zshrc文件的例子,它相當于zsh版的.bashrc文件,這些資源同時也提供如何定制zsh的示例或是示范加強命令行經驗的炫酷技巧。

一些zsh功能可和bash一起用,但在bash上設置、配置更加復雜,這也解釋了為什么人們有多頁.bashrc文件。如果是Shell的高度使用者,zsh會是吸引你用來取代bash的選擇。它的使用快速且簡單,而它的一些重要功能也讓與shell的互動更有趣。

原文:http://www.searchsv.com.cn/showcontent_49287.htm

總結

以上是生活随笔為你收集整理的Zsh vs. Bash不完全对比解析,zsh是一种更强大的被成为“终极”的Shell的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。