跳到主要內容
黯羽輕揚每天積累一點點

常用簡單命令_bash 筆記 2

免費2017-02-18#Tool#shell find#shell统计行数#shell拆分日志#shell查找文件#shell md5

記錄常用的簡單 shell 命令

cat(concatenate)

讀取、顯示、拼接檔案內容

把來自標準輸入的內容和檔案內容拼接起來:

echo 'from stdin' | cat - test.sh

cat 命令用 - 表示標準輸入

If file is a single dash (`-') or absent, cat reads from the standard input.

其他常用功能選項:

# 給檔案內容添上行號
cat -n test.sh
# 把檔案中的連續多個空行壓縮成一個
cat -s test.sh

find

基本規則

從檔案目錄向下走訪,匹配符合條件的,處理

# 列出目前目錄下所有檔案/資料夾、子檔案/資料夾
find .
# 用 \0 分隔(檔案路徑裡有換行字元時有用)
find . -print0

# 萬用字元
find -name "*.js"
# 忽略大小寫
find -iname "*.js"
# 多條件
find . \( -name "e*" -o -name "s*" \)
# 路徑匹配
find ../tnode -path "*node*"
# 與 -path 一樣,只是參數為正規表示式
find . -regex ".*/e.*h$"
# 忽略大小寫
find . -iregex ".*/e.*h$"

# 否定參數(獨立參數,可以配合 -name/path/regex 等用)
find . ! -iregex ".*/e.*h$"
# 例如排除路徑含有 node_modules 的
find ../tnode ! -regex ".*node_modules.*"

# 指定目錄深度,-maxdepth 1 表示向下找 1 級(也就是 .. 的孩子,不找孫子)
find .. -name "*.js" -maxdepth 1
# 也可以指定起始深度,-mindepth 2 -maxdepth 2 表示只在 .. 的孫子中找,不找兒子也不找孫子的兒子
find .. -name "*.js" -mindepth 2 -maxdepth 2
# 單獨用 -mindepth 找超過指定深度的檔案(找深路徑 lib)
find .. -regex ".*node_modules*.*.js$" -mindepth 20

按檔案類型搜尋

# 指定檔案/資料夾,-type f 表示只輸出檔案
find ../tnode ! -regex ".*node_modules.*" -type f

P.S. 參數順序會影響搜尋效率,比如先檢查深度再過濾類型更快

檔案類型與 type 參數值對應關係:

普通檔案:f
符號連結:l
目錄:d
面向字元的裝置檔案:c
面向區塊的裝置檔案:b
通訊端 (Socket):s
FIFO:p

按時間搜尋

每個檔案有 3 種時間戳記:

存取時間:-atime
修改時間:-mtime
狀態改變時間:-ctime

參數值為整數,表示天數,可以前綴 +-,分別表示大於、小於,例如:

# 找出父級目錄中,昨天到現在存取過的檔案
find .. -type f -atime -1

也有以分鐘為單位的:

# -amin, -mmin, -cmin
find .. -type f -amin $((-1 * 60 * 24))

還可以指定一個檔案作為參照,找出更新的(修改時間更近的)檔案:

# 找出父級目錄中,比 ~/.bash_profile 更新的檔案
find .. -type f -newer ~/.bash_profile

按檔案大小搜尋

# 目前目錄下大於 1K 的檔案
find . -type f -size +1k

支援 b 區塊, c 位元組, w 字, k, M, G 單位,注意前面小寫,後兩個大寫,在其他命令裡一般也是這樣,例如 split

其他用法

# 查找並刪除
find . -type f -name "*.tmp" -delete
# 匹配檔案權限
find . -type f -perm 777 -print
find . -type f -user ayqy

-exec 結合執行其他命令

# 查找並格式化輸出
find . -type f -exec printf "file: %s\n" {} \;
# 查找並備份
find . -type f -mtime +7 -exec cp {} bak/ \;

P.S. 末尾跳脫分號用來表示 -exec 參數值結束,必須要有

-exec 只能執行一條命令,需要執行多條的話,把命令寫入檔案再執行,例如把備份命令寫入 bak.sh

#!/bin/bash
BAK_DIR=bak

if ! test -e "$BAK_DIR";
then
    mkdir "$BAK_DIR"
fi

for file in "$@";
do
    cp $file "$BAK_DIR"
done

再查找執行:

find . -type f -mtime +7 -exec ./bak.sh {} \;

-prune 排除不需要查找的東西:

# 跳過 .git 和 node_modules 目錄
find . \( -name ".git" -prune \) -o \( -name "node_modules" -prune \) -o \( -type f -print \)

xargs

xargs 命令把從 stdin 接到的數據重新格式化,作為參數提供給其他命令,緊跟在管道操作符之後,基本形式:

cmd | xargs

把多行輸入轉換成單行輸出:

# 把換行字元換成空格
cat test.sh | xargs

把單行輸入轉換成多行輸出:

# 按每行參數數量斷開
echo "1 22 3 4 5 6 7" | xargs -n 3

-d 指定分隔符,實現字串 split

# split
echo "1,2,3,4" | xargs -d ,
# `-d` 參數是 GNU 擴充,FreeBSD 和 Mac 上沒有,用其他方法完成
echo "1,2,3,4" | tr , ' '

-I 指定替換字串:

# replace
echo "1 2 3 4" | xargs -n 1 -I {} find {}.txt

find 結合 xargs

# 查找並刪除
find . -type f -name "*.tmp" -print0 | xargs -0 rm -f

這裡的 -print0xargs -0\0 作為分隔符,避免 temp file.tmp 之類的含有預設分隔符的檔案名稱被拆成兩個參數

統計程式碼行數:

find . -type f -name "*.sh" -print0 | xargs -0 wc -l

對一個參數執行多條命令:

# 與上面的 replace 作用相同
echo '1\n2\n3\n4' | (while read arg; do find $arg.txt; done)

xargs 對每個參數只能執行一條命令,改用子 shell 中迴圈讀取的話,能在迴圈體裡執行多條命令

P.S. 這裡的括號是圓括號擴充運算子,開子 shell 執行括號裡的命令,不是前面的條件分組,不要跳脫括號

tr(translate)

對來自標準輸入的字元進行替換、刪除和壓縮,用來做字串處理

#  大小寫轉換
echo 'Ho Hoho hoho' | tr 'a-z' 'A-Z'

如果兩個字元集合大小不一樣,就把後一個集合用其最後一個字元補足,例如:

# 結果是 ABC XXX
echo 'abc xyz' | tr 'a-z' 'A-X'

P.S. 定義字元集合的形式是「起始字元-終止字元」,結果不是一個連續的字元序列的話,就當作 3 個普通字元

注意:tr 只是對輸入的每個字元做映射,沒有字串匹配和替換,是字元級的操作,不是字元序列(字串)級的

其他常用選項:

# -d 刪除字元
# 結果是 a, a , 579
echo 'hohoa, hoa 123, 4579' | tr -d 'ho0-4'
# -c 得到補集,一般與 -d 結合刪除補集裡的字元,只保留給定字元集合裡的
# 結果是 hohoho1234
echo 'hohoa, hoa 123, 4579' | tr -d -c 'ho0-4'
# -s 壓縮字元(把連續的重複字元換成一個)
# 結果是 ha, ha
echo 'hhhhhha, ha' | tr -s 'a-z'

用字元類別(character class)作為集合:

# 大小寫轉換
echo '124abcX1' | tr '[:lower:]' '[:upper:]'

其他字元類別可以透過 man tr 查看

md5sum, sha1sum

這兩個命令用來計算校驗和,例如:

# 求檔案 md5
# 結果是 `32 個字元的 16 進位字串 檔案名稱`
md5sum test.sh

P.S. Mac 預設沒有 md5sum, sha1sum,需要額外安裝

用 md5 檔案校驗

# 用 md5 檔案檢查檔案是否正確
md5sum -c file.md5

md5deep 生成資料夾的 md5,需要額外安裝(sha1deep 與之類似):

# yum 安裝
yum install md5deep
# 求資料夾的 md5
# -r 遞迴,-l 生成相對路徑(預設是絕對路徑)
md5deep -rl dir > dir.md5
# 用所有 md5 檔案校驗
md5sum *.md5

sort & uinq

sort 命令對行排序,uniq 去重,一般配合使用,例如:

# 對 file.txt 內容每行按字典序排序,並去重
sort file.txt | uniq
# 或者
sort -u file.txt

預設按字典序升序排序,-n 按數值排序,-r 降序:

# 如果字母數字都有,字母在前
sort -n file.txt
sort -r file.txt

其他常用選項:

# 檢查檔案內容是否有序,是否按數值序用 -nC
# 返回值為 0,表示有序
sort -C file.txt; echo $?
# 按第 2 列排序
sort -k 2 file.txt
# 按第 2 個字元到第 5 個字元排序
sort -k 2,5 file.txt
# 用 \0 作為分隔符(透過管道結合其他命令時有用)
sort -z file.txt
# 忽略前導空白字元
sort -b file.txt

uniq 命令只能用於有序的輸入,所以一般結合 sort 使用:

# 只顯示唯一的行(出現多於 1 次的行都被濾掉)
uniq -u sorted.txt
# 統計各行出現次數
uniq -c sorted.txt
# 找出重複的行
uniq -d sorted.txt

去重也可以指定 key

# -s 跳過前幾個字元,-w 指定 key 的長度
uniq -s 3 -w 2 sorted.txt

P.S. Mac 沒有 -w 選項

split

split 命令用來分割大檔案,例如:

# 把 data.txt 分割成 1k 的多個檔案
split -b 1k data.txt

預設生成 xaa, xab, xac... 之類的檔案名稱,預設嚴格按大小拆分,行可能會被截斷,甚至一個漢字被拆開

生成的檔案名稱可以手動指定,最後一個參數是前綴,預設是 x-a 指定後綴長度,其他選項請查看 man split

也可以按行分割檔案:

# 每個檔案 10 行,檔案名稱為 `small.aa, small.ab, small.ac...`
split -l 10 test.sh 'small.'

P.S. 原來有這種命令,當時為了拆分 sql 備份檔案,特意找了一個能夠編輯大檔案的文字編輯器,手動分割的...

P.S. 另一個更強大的檔案分割命令是 csplit ,常用來分割日誌檔案,能夠以是否存在指定文字內容為條件拆分

其他小技巧

暫存檔案命名

Ubuntu, Debian 中有 tempfile 命令,用來生成暫存檔案名稱(一個隨機字串),其他環境可以使用 RANDOM 環境變數,或者目前程序 ID:

# 取 RANDOM 環境變數的值
$RANDOM
# 取目前程序 ID
$$

字串提取

%, %%, #, ## 運算子提供了強大的字串提取功能:

file=logo.png
# 提取檔案名稱
filename=${file%.*}
echo filename:$filename
# 提取副檔名
ext=${file##*.}
echo ext:$ext

用法如下:

# 從 var 的值中刪掉 % 右側萬用字元所匹配的字串,從右邊向左匹配
${var%.*}
# %% 貪婪匹配,會找出最長字串,% 匹配最短字串
${var%.*}

# 從 var 的值中刪掉 # 右側萬用字元所匹配的字串,從左向右匹配
${var#*.}
# 對應的貪婪匹配
${var##*.}

提取副檔名應該用 ## 貪婪匹配,因為 file.txt.md5 之類的檔案名稱含有多個 .

評論

暫無評論,快來發表你的看法吧

提交評論