日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

Bash Cookbook 学习笔记 【高级】

發(fā)布時(shí)間:2025/4/5 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Bash Cookbook 学习笔记 【高级】 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

Read Me

  • 本文是以英文版<bash cookbook> 為基礎(chǔ)整理的筆記,力求脫水
  • 【高級(jí)】部分,涉及腳本安全、bash定制、參數(shù)設(shè)定等高階內(nèi)容
  • 本系列其他兩篇,與之互為參考

    • 【基礎(chǔ)】?jī)?nèi)容涵蓋bash語(yǔ)法等知識(shí)點(diǎn)。傳送門(mén)
    • 【中級(jí)】?jī)?nèi)容包括工具、函數(shù)、中斷及時(shí)間處理等進(jìn)階主題。傳送門(mén)
  • 所有代碼在本機(jī)測(cè)試通過(guò)

    • Debian GNU/Linux 9.2 (stretch)
    • GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)
  • 2018.02.03 更新 【七】編寫(xiě)安全的腳本.輸入驗(yàn)證

約定格式

# 注釋:前導(dǎo)的$表示命令提示符 # 注釋:無(wú)前導(dǎo)的第二+行表示輸出# 例如: $ 命令 參數(shù)1 參數(shù)2 參數(shù)3 # 行內(nèi)注釋 輸出_行一 輸出_行二$ cmd par1 par1 par2 # in-line comments output_line1 output_line2

七、編寫(xiě)安全的腳本

安全是一個(gè)過(guò)程,而不是某種成品、對(duì)象、或技術(shù),且沒(méi)有終點(diǎn)。 -- Bruce Schneier

比如:

  • 沒(méi)有權(quán)限提升的隱患
  • 不會(huì)意外執(zhí)行rm -rf /這樣的破壞性代碼
  • 不會(huì)泄露密碼等敏感信息
  • 運(yùn)行中斷時(shí)清理現(xiàn)場(chǎng) (fail gracefully)
  • 對(duì)用戶(hù)的錯(cuò)誤輸入有容錯(cuò)和檢查
  • 只使用可信賴(lài)的外部文件
  • 代碼簡(jiǎn)潔、可讀性強(qiáng),文檔完善,功能明確

當(dāng)然,以上也適用于所有的軟件。

展開(kāi)細(xì)說(shuō),先從腳本頭#!開(kāi)始

shebang !

shebang#!出現(xiàn)在任何腳本的第一行,它告訴內(nèi)核,該用什么解釋器來(lái)處理該文件。

#!/bin/bash

同時(shí),內(nèi)核也會(huì)接收解釋器(比如bash)后邊跟的一個(gè)唯一參數(shù)(如果用戶(hù)提供的話)。該參數(shù)會(huì)被利用來(lái)進(jìn)行解釋器欺騙(Interpreter Spoofing)。

所以,最好在該位置用減號(hào)-占位

#!/bin/bash -

但這樣的路徑/bin/是硬編碼的,又會(huì)產(chǎn)生各OS之間可移植性的問(wèn)題。

解決方法:通過(guò)原生的env命令,自動(dòng)識(shí)別bash的安裝位置

$ env ... SHELL=/bin/bash ...

所以,這樣寫(xiě)行了嗎?

#!/usr/bin/env bash -

還是不行。文件找不到了。

/usr/bin/env: ‘bash -’: No such file or directory

linux和很多其他unix的env,不允許后邊跟兩個(gè)或以上的參數(shù),這里參數(shù)指的是bash和-。BSD和Solaris等極少數(shù)除外。

所以,可移植和安全性有點(diǎn)魚(yú)和熊掌的意思,需要自己權(quán)衡輕重

# 輕安全,重移植 #!/usr/bin/env bash# 重安全,輕移植 #!/bin/bash -

最后,再提一個(gè)細(xì)節(jié)。有時(shí)候,你會(huì)看到有些腳本的#!和/bin/解釋器之間有一個(gè)空格。這是為了向前兼容。很老的系統(tǒng)里需要這個(gè)空格。現(xiàn)在的話,可寫(xiě)可不寫(xiě)了。

#! /bin/bash

安全路徑 $PATH getconf

shebang之后,寫(xiě)所有其他代碼之前,請(qǐng)先設(shè)置安全路徑

  • 第一種寫(xiě)法:

顯式聲明一遍PATH變量,并再次注冊(cè)到運(yùn)行環(huán)境。反斜杠用于禁用別名擴(kuò)展功能。

PATH='/usr/local/bin:/bin:/usr/bin' \export PATH
  • 另一種方法:
export PATH=$(getconf PATH)

getconf用于獲取系統(tǒng)參數(shù)設(shè)置

$ getconf -a | grep PATH PATH_MAX 4096 _POSIX_PATH_MAX 4096 PATH /bin:/usr/bin CS_PATH /bin:/usr/bin

但第二種寫(xiě)法還存在個(gè)問(wèn)題:$(變量)移植性不如單引號(hào)``。

而且,變量聲明和export注冊(cè)放在一條語(yǔ)句內(nèi)也不是通用的寫(xiě)法

# 移植性不好 export var='foo'# 最好拆開(kāi)來(lái)寫(xiě) var='foo'; export var
  • 第三種方法:

既然注冊(cè)$PATH路徑變量的目的是為了查找工具,那么,可不可以直接指定各工具的路徑呢?

這樣寫(xiě)腳本會(huì)很長(zhǎng)。所以最好打包進(jìn)一個(gè)函數(shù)內(nèi),供各腳本調(diào)用。

#!/usr/bin/env bash # 工具查找# 復(fù)制、移動(dòng)、刪除,每個(gè)系統(tǒng)都一樣 _cp='/bin/cp' _mv='/bin/mv' _rm='/bin/rm'# 分支判斷 case $(/bin/uname) in'Linux')_cut='/bin/cut'_nice='/bin/nice'# [其他工具];;'SunOS')_cut='/usr/bin/cut'_nice='/usr/bin/nice'# [其他工具];;# [其他系統(tǒng)環(huán)境] esac

當(dāng)前路徑 ./

為了減少輸入量,有些用戶(hù)習(xí)慣把當(dāng)前路徑.或空路徑(尾部:,或中間::),也加進(jìn)$PATH變量。

# 當(dāng)前路徑 PATH=.:$PATH PATH=$PATH:.# 空路徑 PATH='/bin:/usr/bin:' PATH='/bin::/usr/bin'

從安全的角度,這是很不好的習(xí)慣,尤其是對(duì)root賬戶(hù)。

因?yàn)閷?duì)命令進(jìn)行路徑搜索是按$PATH各項(xiàng)依次查找的。

當(dāng)前路徑在搜索鏈的存在會(huì)造成一些不可控的結(jié)果。

  • 設(shè)想一種情況:

如果當(dāng)前路徑放在最前

$ PATH='.:/bin:/usr/bin'; export PATH

此時(shí)在tmp目錄執(zhí)行l(wèi)s時(shí),系統(tǒng)會(huì)先嘗試運(yùn)行/tmp/ls,而這個(gè)ls如果意外存在的話,極可能是木馬命令。

$ cd /tmp; pwd /tmp$ ls # 此處中招了
  • 再假設(shè)一種情況:

點(diǎn)號(hào).放在最后

$ PATH='/bin:/usr/bin:.'; export PATH

你機(jī)子上恰好裝有一款叫midnight commander程序,它的命令恰好是mc。你在移動(dòng)文件時(shí)mv不小心寫(xiě)成了mc。本該執(zhí)行的/bin/mv變成了./mc

$ PATH='/bin:/usr/bin:.'; export PATH$ mc file1 file2 # 此處再次中招

以上兩個(gè)例子有點(diǎn)極端。但所謂安全,不正是預(yù)防此類(lèi)小概率事件嗎?

禁用別名

惡意的別名類(lèi)似木馬(trojan),可以誘導(dǎo)用戶(hù)執(zhí)行不安全的命令。

看個(gè)簡(jiǎn)單的例子。

$ alias unalias=echo$ alias builtin=ls$ builtin unalias vi ls: unalias: No such file or directory ls: vi: No such file or directory$ unalias -a -a

通過(guò)使用別名,原生的builtin和unalias都被其他命令覆蓋了。

刪除所有的別名,可以消除隱患。

\unalias -a

敏感信息

哈希表 hash

當(dāng)前運(yùn)行環(huán)境下,執(zhí)行過(guò)的命令會(huì)被添加到哈希表(hash),用于提高再次調(diào)用時(shí)的訪問(wèn)速度。

污染(poison)哈希表

# dog指向cat $ hash -p /bin/cat dog$ hash -l builtin hash -p /bin/cat cat builtin hash -p /bin/cat dog builtin hash -p /bin/stty stty builtin hash -p /usr/bin/clear clear

-r開(kāi)關(guān)可用于清空哈希表

# 清理命令路徑下的所有哈希值 hash -r

核轉(zhuǎn)儲(chǔ) core dump

core dump也被譯為內(nèi)核轉(zhuǎn)儲(chǔ)或核心轉(zhuǎn)儲(chǔ),這里的內(nèi)核有別于操作系統(tǒng)內(nèi)核(kernel)

  • core : 應(yīng)用程序在崩潰瞬間的內(nèi)存等運(yùn)行環(huán)境的快照,用于調(diào)試和分析
  • kernel : Linux系統(tǒng)最核心的那部分代碼

被轉(zhuǎn)儲(chǔ)的內(nèi)存頁(yè)面可能含有密碼等信息,最好禁用該功能。

且最好是寫(xiě)入系統(tǒng)級(jí)的配置文件中,如/etc/profile或~/.bashrc

# 禁用腳本和相關(guān)進(jìn)程的內(nèi)核轉(zhuǎn)儲(chǔ)功能 可參考`man 1 bash`的相關(guān)章節(jié) ulimit -H -c 0 --# -H 硬上限 # -c 0 核轉(zhuǎn)儲(chǔ)大小限制為0,即禁用

明文密碼

首先一點(diǎn),千萬(wàn)千萬(wàn)不要像這樣寫(xiě)

$ ./某腳本 -u 用戶(hù) -p 密碼 & [1] 13301

就算輸入密碼時(shí),不回顯到屏幕,也不行

read -s -p "password: " PASSWD;

因?yàn)?#xff0c;以參數(shù)形式傳遞給腳本的密碼,始終是以明文的形式存在,通過(guò)ps進(jìn)程列表,或以核轉(zhuǎn)儲(chǔ)的形式一覽無(wú)余

$ psPID TTY TIME CMD2348 pts/1 00:00:00 bash9661 pts/1 00:00:00 ps 13301 pts/1 00:00:00 ./某腳本 -u 用戶(hù) -p 密碼 &

如果避免不了要使用明文密碼,可以單獨(dú)放進(jìn)其他用戶(hù)沒(méi)有查看權(quán)限的文件中

$ ./某問(wèn)題腳本 ~.隱藏目錄/密碼文件

像這樣間接引用,至少避免明文暴露的問(wèn)題。

crypt或其他密碼哈希可行嗎?

首先,哈希是不可逆的,你無(wú)法還原回原來(lái)的明文。也就是無(wú)法訪問(wèn)那些需要該明文密碼的數(shù)據(jù)庫(kù)。如此,你只能取消數(shù)據(jù)庫(kù)的密碼保護(hù),有點(diǎn)得不償失。

哈希給你的,只是一種"安全"的假象。還不如用明文。

對(duì)于明文,一種簡(jiǎn)單的防護(hù)措施,可以是ROT-13的形式,這個(gè)在前邊介紹過(guò)。或用47個(gè)字符的擴(kuò)展版本,除了大小寫(xiě)26個(gè)字母外,還支持標(biāo)點(diǎn)。

$ ROT13=$(echo password | tr 'A-Za-z' 'N-ZA-Mn-za-m')$ ROT47=$(echo password | tr '!-~' 'P-~!-O')

這種打亂字母順序的方式,有總比沒(méi)有好點(diǎn),至少不會(huì)讓你產(chǎn)生"安全"的假象。

比以上更好的,是sudo,或SSH加密會(huì)話。后邊再展開(kāi)來(lái)談。

文件權(quán)限 rwxrwxrwx

默認(rèn)掩碼 umask

umask是bash原生的命令,通過(guò)掩碼改變創(chuàng)建文件(包括目錄)時(shí)的默認(rèn)權(quán)限。

用戶(hù)組其他八進(jìn)制
原來(lái)的默認(rèn)權(quán)限rwxrwxrwx
二進(jìn)制111111111777
掩碼位001011011133
掩碼后默認(rèn)權(quán)限110100100644
rw-r--r--
# 注意:該設(shè)置對(duì)命令行已被重定向的文件不會(huì)產(chǎn)生影響 # 設(shè)置成變量形式,便于根據(jù)需要修改 UMASK=002 umask $UMASK

偵測(cè)外部可寫(xiě)目錄 【腳本】

外部可寫(xiě)(world writable)目錄,是任何其他用戶(hù)都有可寫(xiě)權(quán)限的目錄。當(dāng)然,你肯定不希望此類(lèi)權(quán)限出現(xiàn)在根用戶(hù)的$PATH中。

最好能有個(gè)腳本,能檢查指定路徑下,此類(lèi)不安全的目錄是否存在。運(yùn)行效果類(lèi)似這樣:

$ ./chkpath.sh; echo $? ok drwxrwsr-x root staff /usr/local/bin ok drwxr-xr-x root root /usr/bin ok drwxr-xr-x root root /bin ok drwxrwsr-x root staff /usr/local/games ok drwxr-xr-x root root /usr/games 外部可寫(xiě) drwxrwxrwt root root /tmp 符號(hào)鏈接, ok drwxr-xr-x root root /var/run 缺失 /不存在的目錄 2 $ #!/usr/bin/env bash# 統(tǒng)計(jì)異常目錄個(gè)數(shù) exit_code=0# 列舉所有需要檢查的目錄; for dir in ${PATH//:/ } /tmp /var/run /不存在的目錄 ; do# 如果是符號(hào)鏈接[ -L "$dir" ] && printf "%b" "符號(hào)鏈接, "# 如果不是目錄if [ ! -d "$dir" ]; thenprintf "%b" "缺失\t\t\t\t"(( exit_code++ ))else# 顯示目錄自身 | 取 [權(quán)限,用戶(hù),組]三列stat=$(ls -lHd $dir | awk '{print $1, $3, $4}')# 其他用戶(hù)可寫(xiě)if [ "$(echo $stat | grep '^d.......w. ')" ]; thenprintf "%b" "外部可寫(xiě)\t$stat "(( exit_code++ ))elseprintf "%b" "ok\t\t$stat "fifiprintf "%b" "$dir\n" doneexit $exit_code

該腳本的幾個(gè)要點(diǎn)簡(jiǎn)單說(shuō)明一下:

  • 變量切割

${PATH//:/ }將路徑變量PATH/的冒號(hào):替換為空格,格式${變量/分隔符/替換值}。

用$IFS=':'的形式也能切割變量,但靈活性不如符號(hào)替換。

  • for循環(huán)

for循環(huán)用于實(shí)現(xiàn)路徑遍歷,它的明顯優(yōu)點(diǎn)是有很好的擴(kuò)展性:

你可以添加任意目錄進(jìn)來(lái)

for dir in 目錄1 目錄2 ...; do... done

也可以在循環(huán)體內(nèi)進(jìn)行任意的條件測(cè)試

for dir in ...; do[ -L "$dir" ] && ...if [ ! -d "$dir" ]; then...else...if [ ... ]; then...... done
  • -d開(kāi)關(guān)

ls -d表示只列出目錄自身,不展示其中的內(nèi)容。

$ echo ${PATH//:/ } | xargs ls -ldH drwxr-xr-x 2 root root 4096 Jan 21 08:22 /bin drwxr-xr-x 2 root root 36864 Jan 21 08:23 /usr/bin drwxr-xr-x 2 root root 4096 Jul 13 2017 /usr/games drwxrwsr-x 2 root staff 4096 Jul 24 2017 /usr/local/bin drwxrwsr-x 2 root staff 4096 Jul 24 2017 /usr/local/games

更改權(quán)限 chmod

chmod用于修改目錄及文件權(quán)限。

首先,權(quán)限可以有兩種表現(xiàn)形式:

  • 4位八進(jìn)制的絕對(duì)值
$ chmod 0755 some_script

很多人的習(xí)慣,是只使用后三位數(shù)。第一位是個(gè)特殊位,很少用到。但顯式的寫(xiě)全四位能避免歧義。

  • 符號(hào)表示的相對(duì)值([ugo]+/-/=[rwx])
$ chmod -x some_script$ chmod ugo+rx some_script

相對(duì)值假設(shè)你知道原來(lái)的權(quán)限,帶有主觀性。絕對(duì)值不會(huì)造成誤判,更保險(xiǎn)一些。

修改完之后最好用ls -l再確認(rèn)一遍。

關(guān)于批量修改:

-R遞歸形式是不建議的。它會(huì)將子目錄都設(shè)為不可執(zhí)行,這樣,你就無(wú)法訪問(wèn)這些目錄了。因?yàn)閏d命令是需要可執(zhí)行權(quán)限的

$ chmod -R 0644 some_directory

正確的寫(xiě)法,是對(duì)文件和目錄區(qū)別對(duì)待,以find | xargs的組合方式進(jìn)行批量修改

$ find some_directory -type f | xargs chmod 0644 # 文件 $ find some_directory -type d | xargs chmod 0755 # 目錄

創(chuàng)建新目錄并設(shè)置權(quán)限,兩個(gè)動(dòng)作可以用一條命令完成,避免分開(kāi)執(zhí)行兩條命令時(shí),產(chǎn)生競(jìng)態(tài)(race condition)的隱患。

$ mkdir -m mode new_directory

批量修改權(quán)限前,你可能需要對(duì)整個(gè)系統(tǒng)或特定目錄的權(quán)限設(shè)置先做備份。

備份文件系統(tǒng)的元數(shù)據(jù) 【腳本】

#!/usr/bin/env bash # 文件名 archive_meta.shprintf "%b" "權(quán)限\t用戶(hù)\t組\t大小\t修改時(shí)間\t文件描述\n" > archive_file find / \( -path /proc -o -path /mnt -o -path /tmp -o -path /var/tmp \ -o -path /var/cache -o -path /var/spool \) -prune \ -o -type d -printf 'd%m\t%u\t%g\t%s\t%t\t%p/\n' \ -o -type l -printf 'l%m\t%u\t%g\t%s\t%t\t%p -> %l\n' \ -o -printf '%m\t%u\t%g\t%s\t%t\t%p\n' >> archive_file

其中的(-path /foo -o -path ...) -prune句段用于排除不需要備份的路徑。-printf進(jìn)行格式化輸出。效果如下:

$ sudo ./archive_meta.sh $ head archive_file 權(quán)限 用戶(hù) 組 大小 修改時(shí)間 文件描述 d755 root root 4096 Tue Oct 31 04:45:47.2825806270 2017 // d555 root root 0 Fri Jan 26 06:35:45.5240001190 2018 /sys/ d755 root root 0 Fri Jan 26 06:35:45.5360001780 2018 /sys/kernel/ ...

這個(gè)腳本功能比較簡(jiǎn)單,只作為說(shuō)明用。更專(zhuān)業(yè)的文件備份和完整性檢查,可參考Tripwire等工具。

特殊權(quán)限 setuid setgid

在腳本中設(shè)置特殊位setuid (用戶(hù) user)和setgid (組 group),造成的混亂比解決的問(wèn)題,要多得多。強(qiáng)烈不建議使用。

簡(jiǎn)單介紹一下。

  • 如何設(shè)置:

先分別創(chuàng)建兩個(gè)普通的目錄和文件

$ mkdir suid_dir sgid_dir; touch suid_file sgid_file; ls -l total 8 drwxr-xr-x 2 jimhs jimhs 4096 Jan 26 11:58 sgid_dir/ -rw-r--r-- 1 jimhs jimhs 0 Jan 26 11:58 sgid_file drwxr-xr-x 2 jimhs jimhs 4096 Jan 26 11:58 suid_dir/ -rw-r--r-- 1 jimhs jimhs 0 Jan 26 11:58 suid_file

四位權(quán)限絕對(duì)值的第一位數(shù),4和2,就是setuid位和setgid位

$ chmod 4755 suid_dir suid_file $ chmod 2755 sgid_dir sgid_file

再次查看,已經(jīng)設(shè)置好了。用戶(hù)和組的x都變成了s

$ ls -l total 8 drwxr-sr-x 2 jimhs jimhs 4096 Jan 26 11:58 sgid_dir/ -rwxr-sr-x 1 jimhs jimhs 0 Jan 26 11:58 sgid_file* drwsr-xr-x 2 jimhs jimhs 4096 Jan 26 11:58 suid_dir/ -rwsr-xr-x 1 jimhs jimhs 0 Jan 26 11:58 suid_file*
  • 測(cè)試是否已設(shè)置:

[ -u suid_dir ]及[ -g sgid_file ]用于對(duì)用戶(hù)和組條件測(cè)試。

這兩個(gè)值會(huì)改變創(chuàng)建和從屬關(guān)系,導(dǎo)致不可控的權(quán)限泄漏。這也是造成混亂的源頭。所以,沒(méi)有關(guān)注就沒(méi)有傷害~

隔離的環(huán)境

隨機(jī)數(shù) $RANDOM

在腳本運(yùn)行環(huán)境,使用隨機(jī)數(shù)命名的臨時(shí)目錄及文件,可以增加非法訪問(wèn)的難度。

最簡(jiǎn)單的隨機(jī)數(shù)生成方式,是使用bash的內(nèi)置變量${RANDOM}。

$ echo ${RANDOM}${RANDOM}${RANDOM} 68981103829905

0700和0600權(quán)限保證了其他用戶(hù)沒(méi)有訪問(wèn)權(quán)限。

# 隨機(jī)臨時(shí)目錄 until [ -n "$temp_dir" -a ! -d "$temp_dir" ]; dotemp_dir="/tmp/自定義前綴.${RANDOM}${RANDOM}${RANDOM}" donemkdir -p -m 0700 $temp_dir \|| { echo "FATAL: 無(wú)法創(chuàng)建臨時(shí)目錄'$temp_dir': $?"; exit 100 }# 隨機(jī)臨時(shí)文件 temp_file="$temp_dir/自定義前綴.${RANDOM}${RANDOM}${RANDOM}"touch $temp_file && chmod 0600 $temp_file \|| { echo "FATAL: 無(wú)法創(chuàng)建臨時(shí)文件'$temp_file': $?"; exit 101 }# 退出前記得刪除臨時(shí)目錄 cleanup="rm -rf $temp_dir" trap "$cleanup" ABRT EXIT HUP INT QUIT

相比起馬上要介紹的其他方法,${RANDOM}雖然只能生成包含數(shù)字的隨機(jī)數(shù),但腳本寫(xiě)起來(lái)結(jié)構(gòu)簡(jiǎn)單,簡(jiǎn)單意味著健壯。移植性好。

也可以這樣生成隨機(jī)數(shù):

$ echo $( (last; who; free; date; echo $RANDOM) | md5sum | cut -d' ' -f1 ) c0b5676e55987de62432117842247286

即,將一組無(wú)規(guī)律的命令打包,然后將結(jié)果進(jìn)行哈希,再?gòu)闹腥〕鎏囟ㄗ侄蝸?lái)作為隨機(jī)數(shù)。這樣做有點(diǎn)取巧,只是提供一種思路。

更專(zhuān)業(yè)的實(shí)現(xiàn)方式,當(dāng)然是使用mktemp和/dev/urandom,但考慮到不是任何系統(tǒng)都支持,為了保證腳本的健壯性,避免不了各種繁瑣的驗(yàn)證和錯(cuò)誤處理。

創(chuàng)建安全的臨時(shí)目錄或文件 【腳本】

# 調(diào)用方法: # $temp_file=$(MakeTemp <file|dir> [path/to/name-prefix]) # 示例: # $temp_dir=$(MakeTemp dir /tmp/$PROGRAM.foo) # $temp_file=$(MakeTemp file /tmp/$PROGRAM.foo)function MakeTemp {# 首先,確保$TMP變量已設(shè)置[ -n "$TMP" ] || TMP='/tmp'local temp_type=''local sanity_check=''# 類(lèi)型 file或dirlocal type_name=$1# 如果未指定前綴,則使用$TMP + templocal prefix=${2:-$TMP/temp} case $type_name infile )temp_type=''ur_cmd='touch'# 條件測(cè)試: 是常規(guī)文件、可讀、可寫(xiě)、只有我有訪問(wèn)權(quán)限sanity_check='test -f $TEMP_NAME -a \-r $TEMP_NAME -a \-w $TEMP_NAME -a \-O $TEMP_NAME';;dir|directory )temp_type='-d'ur_cmd='mkdir -p -m0700'# 條件測(cè)試: 是目錄、可讀、可寫(xiě)、可執(zhí)行、只有我有訪問(wèn)權(quán)限sanity_check='test -d $TEMP_NAME -a \-r $TEMP_NAME -a \-w $TEMP_NAME -a \-x $TEMP_NAME -a \-O $TEMP_NAME';;* ) Error "\n$PROGRAM:MakeTemp 參數(shù)錯(cuò)誤! file或dir." 1;;esac# 先試下mktempTEMP_NAME=$(mktemp $temp_type ${prefix}.XXXXXXXXX)# 失敗的話,則用urandomif [ -z "$TEMP_NAME" ]; thenTEMP_NAME="${prefix}.$(cat /dev/urandom | od -x | tr -d ' ' | head -1)"$ur_cmd $TEMP_NAMEfi# 看下創(chuàng)建好沒(méi)有,沒(méi)有的話只能退出了if ! eval $sanity_check; thenError "\a致命錯(cuò)誤: 無(wú)法創(chuàng)建$type_name with '$0:MakeTemp $*'!\n" 2elseecho "$TEMP_NAME"fi} # MakeTemp函數(shù)結(jié)束

受限控制臺(tái) rbash

rbash即功能受限的控制臺(tái)(restricted bash),比如不允許cd到其他目錄、不允許改變環(huán)境變量等。具體請(qǐng)參考man rbash

使用前,需要做些必要配置:

  • 在/etc/passwd為特定用戶(hù)綁定rbash,比如訪客賬戶(hù)等
  • vi、emacs等可以越權(quán)訪問(wèn)到系統(tǒng)根路徑的危險(xiǎn)程序,全部禁用
  • 安全命令,放入專(zhuān)門(mén)的目錄;$PATH唯一綁定到該目錄

硬幣的另一面:一些實(shí)用的程序被禁用后,肯定也影響到使用體驗(yàn)。而且,總會(huì)有漏網(wǎng)之魚(yú)。所以,rbash也不是絕對(duì)安全的,只不過(guò)是門(mén)上多了一道鎖。

監(jiān)獄 chroot

沒(méi)錯(cuò),這個(gè)是叫監(jiān)獄(jail)。

很好理解,就是把那些可疑的腳本或程序,用chroot關(guān)進(jìn)監(jiān)獄,壞腳本就算要搞破壞,影響也是可控的。

類(lèi)似于構(gòu)建了一道隱形的圍墻,chroot會(huì)把根路徑/綁定到指定的安全目錄(change root)。該目錄的父節(jié)點(diǎn)對(duì)里邊的程序是不可見(jiàn)的。結(jié)合前一節(jié)提到的rbash,很多原本視為“危險(xiǎn)”的程序,就沒(méi)必要再被禁用了。

但有些程序,天生需要被暴露給外邊的網(wǎng)絡(luò),比如各種DNS、HTTP或郵件服務(wù)器等。功能越復(fù)雜,管理成本也越高。

擴(kuò)展閱讀,可參考wiki上關(guān)于強(qiáng)制訪問(wèn)控制MAC的介紹。

權(quán)限提升 sudo

sudo允許授權(quán)用戶(hù)臨時(shí)獲得root賬戶(hù)權(quán)限。

使用前請(qǐng)先花點(diǎn)時(shí)間學(xué)習(xí)該命令、授權(quán)配置工具visudo及/etc/sudoers文件(man sudoers)。

類(lèi)似ALL=(ALL) ALL的授權(quán)濫用,會(huì)架空系統(tǒng)的整套防御機(jī)制。

查看用戶(hù)授權(quán)

$ sudo -l

查看sudo的詳細(xì)設(shè)置

$ sudo sudo -V | less

sudo批量命令時(shí),這樣寫(xiě)是錯(cuò)的。因?yàn)閟udo只能影響到它后邊的第一個(gè)參數(shù)。

sudo 命令1 && 命令2 || 命令3

正確的寫(xiě)法

$ sudo bash -c '命令1 && 命令2 || 命令3'

能用sudo的地方,就不要使用su。

輸入驗(yàn)證

所謂驗(yàn)證,就是定義一種模式,然后將用戶(hù)輸入與之比較,結(jié)果無(wú)外乎兩種,要么匹配,要么不匹配

常用的句法結(jié)構(gòu),可以是簡(jiǎn)單的一條語(yǔ)句

[模式] && 執(zhí)行

復(fù)雜點(diǎn)的,可以是龐大的分支結(jié)構(gòu)

case模式1) 執(zhí)行1 ;;模式2) 執(zhí)行2 ;;... esac

這些在前邊基礎(chǔ)部分的測(cè)試/流程控制都已經(jīng)都介紹過(guò)了。

本節(jié)著重講如何定義驗(yàn)證模式,及如何拆解用戶(hù)提供的選項(xiàng)和參數(shù)。并結(jié)合一些實(shí)例,來(lái)強(qiáng)化學(xué)習(xí)。

最簡(jiǎn)單的匹配語(yǔ)法,是像這樣:

[ 文件名 == *.jpg ] && echo "是jpg文件"# 模式不要用括號(hào)包裹。否則會(huì)被理解為字符本身 [ 文件名 == "*.jpg" ] && echo "是jpg文件"

在這里,星號(hào)*還是作為通配符使用,不要與正則表達(dá)式搞混了。

簡(jiǎn)單匹配 【簡(jiǎn)表】

類(lèi)型匹配方式
*任意字符串,包括null
?任意單字符
[ ... ]匹配括號(hào)內(nèi)的任意字符
[ !... ]不匹配括號(hào)內(nèi)的任意字符
[ ^... ]不匹配括號(hào)內(nèi)的任意字符

簡(jiǎn)單匹配 【腳本】

bash安裝包的examples路徑下,給出了一些輸入驗(yàn)證的示范代碼。

  • 帶正負(fù)號(hào)的數(shù)字驗(yàn)證
#examples/functions/isnum2# 整數(shù) isnum2() {case "$1" in'[-+]' | '') return 1;; # 為空,或只有正負(fù)號(hào)[-+]*[!0-9]*) return 1;; # 有正負(fù)號(hào),但不是數(shù)字[-+]*) return 0;; # OK*[!0-9]*) return 1;; # 不是數(shù)字*) return 0;; # OKesac }# 浮點(diǎn)數(shù) isnum3() {case "$1" in'') return 1;; # 為空*[!0-9.+-]*) return 1;; # 非數(shù)字、正負(fù)號(hào)或小數(shù)點(diǎn)*?[-+]*) return 1;; # 符號(hào)不是首位*.*.*) return 1;; # 小數(shù)點(diǎn)超過(guò)一個(gè)*) return 0;; # OKesac }
  • ip地址驗(yàn)證
# examples/functions/isvalidipis_validip() {case "$*" in""|*[!0-9.]*|*[!0-9]) return 1 ;;esaclocal IFS=. # 以.作為分隔符set -- $* # 將參數(shù)分隔后映射到位置變量[ $# -eq 4 ] &&[ ${1:-666} -le 255 ] && [ ${2:-666} -le 255 ] &&[ ${3:-666} -le 255 ] && [ ${4:-666} -le 254 ] }

bash 2.0之后,引入了雙括號(hào)[[ ]],用以支持更復(fù)雜的匹配語(yǔ)法,并從視覺(jué)上區(qū)別于老式的單括號(hào)[ ]。
其中的雙等號(hào)==也可寫(xiě)為=,但建議用前者。

# 啟用**擴(kuò)展匹配**(extended globbing) shopt -s extglob# 對(duì)大小寫(xiě)不敏感 shopt -s nocasematch# 匹配次數(shù)(關(guān)鍵字1|關(guān)鍵字2) if [[ 文件名 == *.@(jpg|jpeg) ]] then# ...

擴(kuò)展匹配 【簡(jiǎn)表】

類(lèi)型匹配次數(shù)
@( ... )一次
*( ... )零或多次
+( ... )一或多次
?( ... )零或一次
!( ... )不要匹配

如果擴(kuò)展匹配還是不能滿足要求,就該正則表達(dá)式(以下簡(jiǎn)稱(chēng)regex)出場(chǎng)了。

在中級(jí)部分講grep工具時(shí),已經(jīng)介紹過(guò)一些常用語(yǔ)法。

正則表達(dá)式 【簡(jiǎn)表】

其他工具,比如gawk、sed、或是vim等編輯器內(nèi),都支持regex語(yǔ)法,但對(duì)于bash自身而言,唯一一處會(huì)用到regex的地方,就是在[[ ]]這樣的測(cè)試語(yǔ)句中。此時(shí),雙等號(hào)==要改為=~,以區(qū)別于簡(jiǎn)單和擴(kuò)展匹配的語(yǔ)法。

[[ 文件名 =~ [[:alpha:]]{3,6}\.jpg ]] && echo "是jpg文件"

方括號(hào)內(nèi)的方括號(hào),是POSIX字符集合,常用的包括:
[[: alnum :]] [[: graph :]] [[: word :]] [[: alpha :]] [[: ascii :]] [[: blank :]] [[: cntrl :]]
[[: digit :]] [[: lower :]] [[: print :]] [[: punct :]] [[: space :]] [[: upper :]] [[: xdigit :]]

復(fù)雜一點(diǎn)的例子。比如想用數(shù)字編號(hào)重命名CD曲目

$ ls Ludwig Van Beethoven - 01 - Allegro.ogg Ludwig Van Beethoven - 02 - Adagio un poco mosso.ogg Ludwig Van Beethoven - 03 - Rondo - Allegro.ogg Ludwig Van Beethoven - 04 - "Coriolan" Overture, Op. 62.ogg Ludwig Van Beethoven - 05 - "Leonore" Overture, No. 2 Op. 72.ogg $

文件名的結(jié)構(gòu):

  • 帶空格的字母集- 數(shù)字集 - 所有剩下的部分(曲目名稱(chēng).后綴)

進(jìn)一步抽象:

  • (regex1)- (regex2) - (regex3)

所以,最終的regex表達(dá)式:

  • ([[:alpha:][:blank:]]*)- ([[:digit:]]*) - (.*)$

三個(gè)圓括號(hào)包裹的子表達(dá)式,被映射到內(nèi)置變量BASH_REMATCH數(shù)組中,數(shù)組第0項(xiàng)表示整條regex語(yǔ)句,其他分別按1、2、3等一一對(duì)應(yīng)。它也是一個(gè)內(nèi)置變量。

for CDTRACK in * doif [[ "$CDTRACK" =~ "([[:alpha:][:blank:]]*)- ([[:digit:]]*) - (.*)$" ]]thenecho Track ${BASH_REMATCH[2]} is ${BASH_REMATCH[3]}mv "$CDTRACK" "Track${BASH_REMATCH[2]}"fi done

選項(xiàng)與參數(shù) getops $OPTIND $OPTARG

選項(xiàng)(option)有兩種。

一種不帶參數(shù)(argument),類(lèi)似于一個(gè)開(kāi)關(guān),通過(guò)打開(kāi)或關(guān)閉,來(lái)改變腳本的行為

# 分開(kāi) $ ls -a -l -h ... # 合并 $ ls -alh ...

另一種要帶參數(shù)

$ mysql -u 用戶(hù)名

除此之外的,都被視為非選項(xiàng)參數(shù)

以上介紹了四個(gè)概念,用個(gè)完整的例子來(lái)演示:

myscript -a -b alt plow harvest reap

其中:

  • 開(kāi)關(guān)選項(xiàng) -a
  • 帶參選項(xiàng) -b
  • 選項(xiàng)參數(shù) alt
  • 非選項(xiàng)參數(shù) plow harvest reap

在腳本中,如何接收和驗(yàn)證這些選項(xiàng)和參數(shù)?

先貼答案:

#!/usr/bin/env bash #getopts.shaflag= bflag= while getopts 'ab:' OPTION docase $OPTION ina)aflag=1;;b)bflag=1bval="$OPTARG";;?)printf "用法: %s: [-a] [-b value] args\n" $(basename $0) >&2exit 2;;esac doneshift $(($OPTIND – 1))if [ "$aflag" ] thenprintf "選項(xiàng) -a 已提供\n" fi if [ "$bflag" ] thenprintf '選項(xiàng) -b "%s" 已提供\n' "$bval" fi printf "剩下的參數(shù)是: %s\n" "$*"

腳本的核心部分是:

getopts 'ab:' OPTION

內(nèi)置命令getopts,用于接收以減號(hào)-開(kāi)頭的選項(xiàng)。每接收到一個(gè),就放入OPTION變量中,用于后續(xù)處理,并返回TRUE。這樣,wihle循環(huán)到下一圈。如此反復(fù),直至所有選項(xiàng)被耗盡(取完),或遇到兩個(gè)減號(hào)--,這時(shí),返回FALSE。while循環(huán)終止。

getopts可接受的選項(xiàng)范圍在單引號(hào)中定義,這里是a和b。冒號(hào):表示b是帶參選項(xiàng)。如果a是帶參選項(xiàng),則寫(xiě)為'a:b'。選項(xiàng)參數(shù)會(huì)被放入內(nèi)置變量$OPTARG中。

while循環(huán)之后的下一條語(yǔ)句是

shift $(($OPTIND – 1))

$OPTIND內(nèi)置變量用于存放選項(xiàng)和參數(shù)的位置索引,初始值是1。每執(zhí)行一次getopts,該值遞增并指向下個(gè)待處理選項(xiàng)。

所以,以下命令在while循環(huán)停止的時(shí)候,$OPTIND數(shù)值是4,指向"plow"的位置。也即通過(guò)shift右移3次(3=4-1)達(dá)到。

myscript -a -b alt plow harvest reap 位置參數(shù) 1 2 3 4

腳本中,$*用于取完所有剩下的非選項(xiàng)參數(shù)"plow harvest reap"

printf "剩下的參數(shù)是: %s\n" "$*"

運(yùn)行效果:

./getopts.sh -ab alt plow harvest reap 選項(xiàng) -a 已提供 選項(xiàng) -b "alt" 已提供 剩下的參數(shù)是: plow harvest reap

自定義錯(cuò)誤 【腳本】

對(duì)于非法選項(xiàng),getopts會(huì)提供默認(rèn)的錯(cuò)誤警告信息。如需關(guān)閉,可先設(shè)置OPTERR=0。

如需使用自定義的錯(cuò)誤警告,則在getopts定義選項(xiàng)接收范圍時(shí),在最開(kāi)頭的位置用冒號(hào):標(biāo)識(shí)。

getopts ':ab:' OPTION

增加了自定義錯(cuò)誤警告的腳本:

#!/usr/bin/env bash #getopts.shaflag= bflag= # printf "OPTIND: %d\n" $OPTIND#OPTERR=0 while getopts :ab: FOUND do# printf "OPTIND: %d\n" $OPTINDcase $FOUND ina)aflag=1;;b)bflag=1bval="$OPTARG";;\:) # 反斜杠\表示取消對(duì)冒號(hào)轉(zhuǎn)義,下同printf "%s 選項(xiàng)缺少參數(shù)\n" $OPTARGprintf "用法: %s: [-a] [-b value] args\n" $(basename $0)exit 2;;\?)printf "未知選項(xiàng): -%s\n" $OPTARGprintf "用法: %s: [-a] [-b value] args\n" $(basename $0)exit 2;;esac >&2 doneshift $(($OPTIND - 1))if [ "$aflag" ] thenprintf "選項(xiàng) -a 已提供\n" fiif [ "$bflag" ] thenprintf '選項(xiàng) -b "%s" 已提供\n' "$bval" fiprintf "剩下的參數(shù)是: %s\n" "$*"

與前一個(gè)例子不同的幾個(gè)地方:

  • 前導(dǎo)冒號(hào): 當(dāng)你輸入的選項(xiàng)缺少參數(shù)、或選項(xiàng)未定義時(shí),getopts會(huì)分別返回字面的冒號(hào):或問(wèn)號(hào)?。同時(shí),該選項(xiàng)符號(hào)被放入$OPTARG變量,這樣,就便于在定義錯(cuò)誤警告的格式化語(yǔ)句中進(jìn)行引用了。
  • 轉(zhuǎn)義和不轉(zhuǎn)義的區(qū)別: case分支中,冒號(hào):前的反斜杠可寫(xiě)可不寫(xiě)。問(wèn)號(hào)?前要寫(xiě)(即,不做轉(zhuǎn)義)。兩者都寫(xiě),是為了保持一致,更美觀。而前一個(gè)例子的問(wèn)號(hào)前之所以不帶反斜杠,是因?yàn)榘阉旁赾ase語(yǔ)句的最后一條缺省分支中,既表示字面的?(也即getopts的返回值),也表示通配符擴(kuò)展,用來(lái)匹配任意字符。
  • 重定向: 本例中,將整個(gè)case塊都重定向到標(biāo)準(zhǔn)錯(cuò)誤(STDERR 2),比前例每條printf語(yǔ)句單獨(dú)重定向要更好維護(hù)。

運(yùn)行效果:

./getopts.sh -a -b b 選項(xiàng)缺少參數(shù) 用法: getopts.sh: [-a] [-b value] args bash現(xiàn)在的主要維護(hù)者Chet Ramey,在bash源代碼目錄下(examples/scripts/shprompt),給出了一個(gè)輸入驗(yàn)證的完整模板。內(nèi)容太長(zhǎng),這里不貼了。有興趣的讀者可以參考。

總結(jié)

以上是生活随笔為你收集整理的Bash Cookbook 学习笔记 【高级】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。