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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Bash Cookbook 学习笔记 【中级】

發布時間:2023/12/9 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Bash Cookbook 学习笔记 【中级】 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Read Me

  • 本文是以英文版<bash cookbook> 為基礎整理的筆記,力求脫水
  • 2018.01.21 更新完【中級】。內容包括工具、函數、中斷及時間處理等進階主題。
  • 本系列其他兩篇,與之互為參考
    • 【基礎】內容涵蓋bash語法等知識點。傳送門
    • 【高級】內容涉及腳本安全、bash定制、參數設定等高階內容。傳送門
  • 所有代碼在本機測試通過
    • Debian GNU/Linux 9.2 (stretch)
    • GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu)
  • 2018.01.21 更新 【四】工具.sed流處理 【六】日期與時間

約定格式

# 注釋:前導的$表示命令提示符 # 注釋:無前導的第二+行表示輸出# 例如: $ 命令 參數1 參數2 參數3 # 行內注釋 輸出_行一 輸出_行二$ cmd par1 par1 par2 # in-line comments output_line1 output_line2 復制代碼

四、工具

UNIX(Linux)喜歡小而美,不喜歡大而雜

grep 搜索字符串

在當前路徑的所有c后綴文件中,查找printf字符串

$ grep printf *.c both.c: printf("Std Out message.\n", argv[0], argc-1); both.c: fprintf(stderr, "Std Error message.\n", argv[0], argc-1); good.c: printf("%s: %d args.\n", argv[0], argc-1); somio.c: // we'll use printf to tell us what we somio.c: printf("open: fd=%d\n", iod[i]); 復制代碼

當然,也可以像這樣,指定不同的搜索路徑

$ grep printf ../lib/*.c ../server/*.c ../cmd/*.c */*.c 復制代碼

搜索結果的默認輸出格式為“文件名 冒號 匹配行”

可以通過**-h**開關隱藏(hide)文件名

$ grep -h printf *.c printf("Std Out message.\n", argv[0], argc-1); fprintf(stderr, "Std Error message.\n", argv[0], argc-1); printf("%s: %d args.\n", argv[0], argc-1);// we'll use printf to tell us what weprintf("open: fd=%d\n", iod[i]); 復制代碼

或者,不顯示匹配行,而只是用**-c**開關進行對匹配次數進行計數(count)

$ grep -c printf *.c both.c:2 good.c:1 somio.c:2 復制代碼

或者,只是簡單地列出(list)含搜索項的文件清單,可以用**-l**開關

$ grep -l printf *.c both.c good.c somio.c 復制代碼

文件清單可視為一個不包含重復項的集合,便于后續處理,比如

$ rm -i $(grep -l 'This file is obsolete' * ) 復制代碼

有時候,只需要知道是否滿足匹配,而不關心具體的內容,可以使用**-q**靜默(quiet)開關

$ grep -q findme bigdata.file $ if [ $? -eq 0 ] ; then echo yes ; else echo nope ; fi nope 復制代碼

也可以把輸出重定向進/dev/null位桶,一樣實現靜默的效果。位桶(bit bucket)就相當于“位的垃圾桶”,一個有去無回的比特黑洞

$ grep findme bigdata.file >/dev/null $ if [ $? -eq 0 ] ; then echo yes ; else echo nope ; fi nope 復制代碼

經常,你更希望搜索時忽略(ignore)大小寫,這時可以用**-i**開關

$ grep -i error logfile.msgs # 匹配ERROR, error, eRrOr.. 復制代碼

很多時候,搜索范圍并不是來自文件,而是管道

$ 命令1 | 命令2 | grep 復制代碼

舉個例。將gcc編譯的報錯信息從標準錯誤(STDERR, 2)重定向到標準輸出(STDOUT,1),再通過管道傳給grep進行篩選

$ gcc bigbadcode.c 2>&1 | grep -i error 復制代碼

多個grep命令可以串聯,以不斷地縮小搜索范圍

grep 關鍵字1 | grep 關鍵字2 | grep 關鍵字3 復制代碼

比如,與**!!**(復用上一條命令)組合使用,可以實現強大的增量式搜索

$ grep -i 李 專輯/* ... 世界上有太多人姓李 ... $ !! | grep -i 四 grep -i 李 專輯/* | grep -i 四 ... 叫李四的也不少 ... $ !! | grep -i "饒舌歌手" grep -i 李 專輯/* | grep -i 四 | grep -i "饒舌歌手" 李四, 饒舌歌手 <lsi@noplace.org> 復制代碼

-v開關用來反轉(reverse)搜索關鍵字

$ grep -i dec logfile | grep -vi decimal | grep -vi decimate 復制代碼

按關鍵字'dec'匹配,但不要匹配'decimal',也不要匹配'decimate'。因為這里的dec意思是december

... error on Jan 01: not a decimal number error on Feb 13: base converted to Decimal warning on Mar 22: using only decimal numbers error on Dec 16 : 匹配這一行就對了 error on Jan 01: not a decimal number ... 復制代碼

像上邊這樣“要匹配這個,但不要包含那個”...是非常笨重的,就像在純手工地對密碼進行暴力破解。

仔細觀察規律,匹配關鍵字的模式,才是正解。

$ grep 'Dec [0-9][0-9]' logfile 復制代碼

[0-9][0-9]匹配dec后邊的一位或兩位數日期。如果日期是一位數,syslog會在數字后加個空格補齊格式。所以為了考慮進這種情況,改寫如下,

$ grep 'Dec [0-9 ][0-9]' logfile 復制代碼

對于包含空格等敏感字符的表達式,總是用單引號'...'對表達式進行包裹是個良好的習慣。這樣可以避免很多不必要的語法歧義。

當然,用反斜杠**\**對空格取消轉義(escaping)也行。但考慮到可讀性,還是建議用單引號對。

$ grep Dec\ [0-9\ ][0-9] logfile 復制代碼

結合正則表達式(Re),可以實現更復雜的匹配。

正則表達式【簡表】

. # 任意一個字符 .... # 任意四個字符 A. # 大寫A,跟一個任意字符 * # 零個或任意一個字符 A* # 零個或任意多個大寫A .* # 零個或任意個任意字符,甚至可以是空行 ..* # 至少包含一個空行以外的任意字符 ^ # 行首 $ # 行尾 ^$ # 空行 \ # 保留各符號的本義 [字符集合] # 匹配方括號內的字符集合 [^字符集合] # 不匹配方括號內的字符集合 [AaEeIiOoUu] # 匹配大小寫元音字母 [^AaEeIiOoUu] # 匹配不包括大小寫元音的任意字母 \{n,m\} # 重復,最少n次,最多m次 \{n\} # 重復,正好n次 \ {n,\} # 重復,至少n次 A\{5\} # AAAAA A\{5,\} # 至少5個大寫A 復制代碼

舉個實用的例子:匹配社保編號 SSN

$ grep '[0-9]\{3\}-\{0,1\}[0-9]\{2\}-\{0,1\}[0-9]\{4\}' datafile 復制代碼

這么長的正則,寫的人很爽,讀的人崩潰。所以也被戲稱為Write Only.

為了寫給人看,一定要加個注釋的。

為了講解清楚,來做個斷句

[0-9]\{3\} # 先匹配任意三位數 -\{0,1\} # 零或一個橫杠 [0-9]\{2\} # 再跟任意兩位數 -\{0,1\} # 零或一個橫杠 [0-9]\{4\} # 最后是任意四位數 復制代碼

還有一些z字頭工具,可以直接對壓縮文件進行字符串的查找和查看處理。比如zgrep, zcat, gzcat等。一般系統會預裝有

$ zgrep 'search term' /var/log/messages* 復制代碼

特別是zcat,會盡可能地去還原破損的壓縮文件,而不像其他工具,對“文件損壞”只會一味的報錯。

$ zcat /var/log/messages.1.gz 復制代碼

awk 變色龍

awk是一門語言,是perl的先祖,是一頭怪獸,是一只變色龍(chameleon)。

作為(最)強大的文本處理引擎,awk博大精深,一本書都講不完。這里只能挑些最常用和基礎的內容來講。

首先,以下三種傳文件給awk的方式等效:

$ awk '{print $1}' 輸入文件 # 作為參數 $ awk '{print $1}' < 輸入文件 # 重定向 $ cat 輸入文件 | awk '{print $1}' # 管道 復制代碼

對于格式化的文本,比如ls -l的輸出,awk對各列從1開始編號,依次遞增。不是從0,因為$0表示整行。最后一列,記為NF。空格被默認作各列的分隔符,也可以通過-F開關進行自定義。

$1$2$3...$NF
首列第二列第三列...尾列
$0整行
$ ls -l total 4816 drwxr-xr-x 4 jimhs jimhs 4096 Nov 26 02:10 backup drwxr-xr-x 3 jimhs jimhs 4096 Nov 24 08:20 bash ... $ $ ls -l| awk '{print $1, $NF}' # 打印第一行和最后一行 total 4816 drwxr-xr-x backup drwxr-xr-x bash ... 復制代碼

注意到,第五列是文件大小,可以對其大小求和,并作為結果輸出

$ ls -l | awk '{sum += $5} END {print sum}' 復制代碼

ls -l輸出的第一行,是一個total匯總。也正因為該行并沒有“第五列”,所以對上邊的{sum += $5}沒有影響。

但實際上,嚴格來講,應該對這樣的特例做預處理,即,刪掉該行。

首先想到的:可以用之前介紹grep時的**-v**翻轉開關,來去除含'total'的那行

$ ls -l | grep -v '^total' | awk '{sum += $5} END {print sum}' 復制代碼

另一種方法是:在awk腳本內,先用正則定位到total行(第一行),找到后立即執行緊跟的{getline}句塊,因為getline用來接收新的輸入行,這樣就順利跳過了total行,而進入了{sum += $5}句塊。

$ ls -l | awk '/^total/{getline} {sum += $5} END {print sum}' 復制代碼

也就是說,作為awk腳本,各結構塊擺放的順序是相當重要的。

一個完整的awk腳本可以允許多個大括號{}包裹的結構。END前綴的結構體,表示待其他所有語句執行完后,執行一次。與之相對的,是BEGIN前綴,會在任何輸入被讀取之前執行,通常用來進行各種初始化。

作為可編程的語言,awk部分借用了c語言的語法。

可以像這樣,將結構寫成多行

$ awk '{ > for (i=NF; i>0; i--) { > printf "%s ", $i; > } > printf "\n" > }' 復制代碼

也可以把整個結構體塞進一行內

$ awk '{for (i=NF; i>0; i--) {printf "%s ", $i;} printf "\n" }' 復制代碼

以上腳本,將各列逆序輸出:

drwxr-xr-x 4 jimhs jimhs 4096 Nov 26 02:10 backup 變成了 backup 02:10 26 Nov 4096 jimhs jimhs 4 drwxr-xr-x 復制代碼

對于復雜的腳本,可以單獨寫成一個.awk后綴的文件

# # 文件名: asar.awk # NF > 7 { # 觸發計數語句塊的邏輯,即該行的項數要大于7user[$3]++ # ls -l的第3個變量是用戶名} END {for (i in user){printf "%s owns %d files\n", i, user[i]}} 復制代碼

然后通過**-f**文件開關來引用(file)

$ ls -lR /usr/local | awk -f asar.awk bin owns 68 files albing owns 1801 files root owns 13755 files man owns 11491 files 復制代碼

這個腳本asar.awk,遞歸地遍歷/usr/local路徑,并統計各用戶名下的文件數量。

注意:其中用于自增時計數的user[]數組,它的索引是$3,即用戶名,而不是整數。這樣的數組也叫作關聯數組(associative arrays) ,或稱為映射(map),或者是哈希表(hashes)

至于怎么做的關聯、映射、哈希,這些技術細節,awk都在幕后自行處理了。

這樣的數組,肯定是無法用整數作為索引去遍歷了。

所以,awk為此專門定制了一條優雅的for...in...的語法

for (i in user) 復制代碼

這里,i會去遍歷整個關聯數組user,本例是[bin , albing , man , root]。再強調一下,重點是會遍歷“整個”。至于遍歷“順序”,你沒法事先指定,也沒必要關心。

下邊的hist.awk腳本,在asar.awk的基礎上,加了格式化輸出和直方圖的功能。也借這個稍復雜的例子,說明awk腳本中函數的定義和調用:

# # 文件名: hist.awk # function max(arr, big) {big = 0;for (i in user){if (user[i] > big) { big=user[i];}}return big }NF > 7 {user[$3]++} END {# for scalingmaxm = max(user);for (i in user){#printf "%s owns %d files\n", i, user[i]scaled = 60 * user[i] / maxm ;printf "%-10.10s [%8d]:", i, user[i]for (i=0; i<scaled; i++) {printf "#";}printf "\n";}} 復制代碼

本例中還用到了printf的格式化輸出,這里不展開說明。

awk內的算術運算默認都是浮點型的,除非通過調用內建函數int(),顯式指定為整型。

本例中做的是浮點運算,所以,只要變量scaled不為零,for循環體就至少會執行一次,類似下邊的"bin"一行,雖然寥寥68個文件,也還是會顯示一格#

$ ls -lR /usr/local | awk -f hist.awk bin [ 68]:# albing [ 1801]:####### root [ 13755]:################################################## man [ 11491]:########################################## 復制代碼

至于各用戶名輸出時的排列順序,如前所述,是由建立哈希時的內在機制決定的,你無法干預。

如果非要干預(比如希望按字典序,或文件數量)排列的話,可以這樣實現:將腳本結構一分為二,將第一部分的輸出先送給sort做排序,然后再通過管道送給打印直方圖的第二部分。

最后,再通過一個小例子,結束awk的介紹。

這個簡短的腳本,打印出包含關鍵字的段落:

$ cat para.awk /關鍵字/ { flag=1 } { if (flag == 1) { print $0 } } /^$/ { flag=0 } $ $ awk -f para.awk < 待搜索的文件 復制代碼

段落(paragraph),是指兩個空行之間所有的文本。空行表示段落的結束

/^$/會匹配空行。但是,對那些含有空格的“空行”,更精確的匹配是像這樣:

/^[:blank:]*$/ 復制代碼

sed 流處理

@2018.01.20

sed即流編輯器(stream editor)。

可以這么來簡單區分,sed對文本是按掃描。awk則是按掃描。

sed本身就是一個龐大的話題,所以原著******并未過多涉及。

最近看***<Linux命令速查手冊>***(第2版)的時候,發現書中引用的一個鏈接內容不錯,中文翻得也不錯。

所以放在此處,供有求知欲的讀者參考。

并特此感謝原作者Eric Pement,及譯者Joe Hong。

  • USEFUL ONE-LINE SCRIPTS FOR SED (Unix stream editor)
  • SED單行腳本快速參考(Unix 流編輯器)

cut uniq sort 切割 去重 排序

處理格式化數據,經常涉及一系列組合操作:切割、去重、排序。

先看個例子,統計系統里各個shell的頻次

$ cat /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin ... $ $ cut -d':' -f7 /etc/passwd | sort | uniq -c | sort -rn17 /bin/false # 禁止登錄16 /usr/sbin/nologin # 禁止登錄2 /bin/bash1 /bin/sync 復制代碼

將管道拆開,逐項來看:

cut -d':' -f7 /etc/passwd # 以冒號為分隔符,取第七個字段 sort # 預排序 uniq -c # 去重,合計歸總 sort -rn # 由大到小,再次排序 復制代碼

對于cut命令, 常用**-d分隔符(delimiter)開關來做列向切割。tab制表符是默認的分隔符。切割后的各列通過-f**域(field)開關來索引。這點與awk的$1...$NF類似。

$ cat ipaddr.list 10.0.0.20 # lanyard 192.168.0.2 # laptop 10.0.0.5 # mainframe 192.168.0.4 # office 10.0.0.2 # sluggish 192.168.0.12 # speedy 復制代碼

**-f2$2**等效,都能取到第二列

$ cut -d'#' -f2 < ipaddr.list $ $ awk -F'#' '{print $2}' < ipaddr.list 復制代碼

對于列寬度固定的格式化數據,比如ps -l或ls -l的輸出,可以用**-c**列索引(column)開關來定位。第一列索引號是1,依次遞增。

$ ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 0 S 1000 9148 9143 0 80 0 - 5322 - pts/0 00:00:00 bash 0 R 1000 9536 9148 0 80 0 - 7466 - pts/0 00:00:00 ps ... 復制代碼

字符區間[12,15]是PID列,

$ ps -l | cut -c12-15 PID 9148 9536 ... 復制代碼

也可以用開區間,例如用[67,)取CMD至末尾

$ ps -l | cut -c67- CMD bash ps ... 復制代碼

怎么取出下邊方括號內的數據列?

$ cat delimited_data Line [l1]. Line [l2]. Line [l3]. 復制代碼

當然,優雅的解法,肯定是用awk+正則

不過,用cut也行:先剪掉左括號,再剪掉右括號。簡單直接。

$ cut -d'[' -f2 delimited_data | cut -d']' -f1 l1 l2 l3 復制代碼

回到本章最開始的例子,了解一下“去重”。

$ 預排序 | uniq -c | 再次排序 復制代碼

uniq適用于預排序過的序列。**-c開關意思是計數(count),將預排序后的各個相鄰的重復項匯總計數。還有一個-d**開關,用于列出重復項(duplicate)。

當uniq接收到兩個文件作為參數時,第二個文件被用來接收輸出,里邊原有的內容會被覆蓋掉。

$ uniq -d file.in file.out 復制代碼

如果不需要計數,可以用sort的**-u**開關去重(unique):

cut -d':' -f7 /etc/passwd | sort -u 復制代碼

sort命令,有三個開關最為常用:

-r 逆序(reverse)

$ sort -r 復制代碼

-f 混雜(fold),忽略大小寫,即“將大小寫混為一體”

$ sort -f# GNU長格式參數的等效寫法: $ sort -–ignore-case 復制代碼

-n 數字(number) 將排序對象視為數字

舉個例子:對ip地址排序

$ cat ipaddr.list 10.0.0.20 # lanyard 192.168.0.2 # laptop 10.0.0.5 # mainframe 192.168.0.4 # office 10.0.0.2 # sluggish 192.168.0.12 # speedy 復制代碼

用前邊介紹過的cut,先去掉注釋列

$ cut -d# -f1 ipaddr.list 10.0.0.20 192.168.0.2 10.0.0.5 192.168.0.4 10.0.0.2 192.168.0.12 復制代碼$ !! | sort -t . -k 1,1n -k 2,2n -k 3,3n -k 4,4n 10.0.0.2 10.0.0.5 10.0.0.20 192.168.0.2 192.168.0.4 192.168.0.12 復制代碼

先用**-t**指定 域分隔符(field seperator),這里是點號。分隔出四個域。

-k 1,1n,用人話表達,就是"從第一個域(1)的首,直至(,)第一個域(1)的尾,按數字(n)排序"。后邊的2、3、4以此類推。

這是新式的POSIX風格的寫法。如果按舊式(已廢止),要寫成這樣

$ sort -t. +0n -1 +1n -2 +2n -3 +3n -4 復制代碼

一樣丑。舊式寫法就不多介紹了。

sort的排序行為,會受**本地化設置(locale setting)**的影響。所以,如果你發現排序行為跟預期不符,最好先檢查一下該設置。

最后,再介紹一個概念,穩定排序(stable sort)

現在, 我們只希望對第四個數域進行排序:

$ sort -t. -k4n ipaddr.list 10.0.0.2 # sluggish 192.168.0.2 # laptop 192.168.0.4 # office 10.0.0.5 # mainframe 192.168.0.12 # speedy 10.0.0.20 # lanyard 復制代碼

對比原始的ip列表。可以看到,雖然laptop和sluggish行的第四個數是相等的,但排序后sluggish被提到了前邊

$ cat ipaddr.list ... 192.168.0.2 # laptop ... 10.0.0.2 # sluggish ... 復制代碼

這是因為,sort默認會進行last-resort comparison的操作:如果分不出大小,就用其他域值來輔助判斷,進行終極的比較。

這種行為,可以通過**-s**開關(stable)禁用

$ sort -t. -s -k4n ipaddr.list 192.168.0.2 # laptop 10.0.0.2 # sluggish ... 復制代碼

tr wc 轉換 統計

將分號全部替換成逗號

$ tr ';' ',' <源文件 >目標文件 復制代碼

這個是tr(translate)命令最原始的用法。分號和逗號是一對一的替換關系

也可以進行多對一的替換,逗號','會被展開成';:.!?'的長度

$ tr ';:.!?' ',' <源文件 >目標文件 復制代碼

一對多呢?這樣寫是沒有意義的。';:.!?'長出來的部分都會被截斷

$ tr ',' ';:.!?' <源文件 >目標文件 復制代碼

作為文字轉換和替換工具,tr不如sed功能豐富,至少tr不支持正則表達式,所以限制了使用范圍。

但是,tr也內置了一些能處理字符范圍的語法。

比如,大小寫的轉換

$ tr 'A-Z' 'a-z' <源文件 >目標文件 復制代碼$ tr '[:upper:]' '[:lower:]' <源文件 >目標文件 復制代碼

總之記住一點,保證替換和被替換目標長度(或范圍)的一致。否則tr會自動去做補齊和截斷,這可能并不是你所期望的。

ROT-13也稱為回轉13,誕生于古羅馬。通過字母移位實現簡單的加解密。

密文 = ROT13(明文) 明文 = ROT13(密文)

$ cat /tmp/joke Q: Why did the chicken cross the road? A: To get to the other side. 復制代碼$ tr 'A-Za-z' 'N-ZA-Mn-za-m' < /tmp/joke D: Jul qvq gur puvpxra pebff gur ebnq? N: Gb trg gb gur bgure fvqr. 復制代碼$ !! | tr 'A-Za-z' 'N-ZA-Mn-za-m' Q: Why did the chicken cross the road? A: To get to the other side. 復制代碼

DOS/Windows,一行結束的標志是"回車"+"換行",兩個字符。Linux,只有一個字符,"換行"。

可以通過開關**-d**進行刪除(delete)

$ tr -d '\r' <dos文件 >linux文件 復制代碼

這樣,所有的回車鍵都被刪除了。包括行末和行內的。很少會有回車鍵出現在“行內”(inline),但這也是可能的。為了避免誤刪,可以考慮用更專業的轉換工具,比如dos2unix或unix2dos

總結一下除了回車鍵之外的轉義字符:

轉義字符【簡表】

轉義符描述
\ooo1-3個八進制數
\\反斜杠自身
\a
\b退格
\f換頁
\n換行
\r回車
\t制表(水平)
\v制表(垂直)

wc用于字數統計(word count)

$ wc data_file 5 15 60 data_file# 統計行數 $ wc -l data_file 5 data_file# 統計詞數 $ wc -w data_file 15 data_file# 統計字符(字節)數 $ wc -c data_file 60 data_file# 60字節,與ls的結果一致 $ ls -l data_file -rw-r--r-- 1 jp users 60B Dec 6 03:18 data_file 復制代碼

如果希望將統計的結果作為變量,

這樣是不行的

data_file_lines=$(wc -l "$data_file") 復制代碼

因為你會得到"5 data_file",而不是數字5

可以用awk將5提取出來

data_file_lines=$(wc -l "$data_file" | awk '{print $1}') 復制代碼

find locate slocate 查找

如何在大海里撈針?

所謂文件夾(folders),是圖形用戶界面(GUI)里的通俗叫法。更專業(BIGE)的名稱,叫做子目錄(subdirectories)

先從最基本的find開始

在當前路徑(.)查找所有的mp3文件,然后移動到~/songs

$ find . -name '*.mp3' -print -exec mv '{}' ~/songs \; 復制代碼

與前邊介紹的各種單字符命令開關(-d, -n)不同,find使用謂語(predicates)來修飾各種行為,比如上邊的-name,-print,-exec,對應名稱,打印,執行。花括號用于接收找到的文件。

文件名有怪異(odd)字符怎么辦?UNIX玩家眼里,任何非小寫、非數字都是怪異的,比如大寫、空格、各種標點、頭上帶音調的字母等等。

$ find . -name '*.mp3' -print0 | xargs -i -0 mv '{}' ~/songs 復制代碼

-print0告訴find,用空字符\0作為各個文件的分割符。同樣,使用-0告訴xargs,管道前邊傳過來的數據使用\0做為分隔符。

因為mv移動完一個文件,才能移動下一個。所以,還特別使用了-i開關,讓參數(mp3文件)一個一個的傳進花括號內。

對于可以一次處理一批文件的命令,比如chmod,可以批量修改很多文件的權限。這時,xargs會把管道傳過來的文件流, 一次性的傳給chmod處理。這樣效率就很高。

$ find some_directory -type f -print0 | xargs -0 chmod 0644 復制代碼

如果這樣寫,處理效率就低了

$ find some_directory -type f -print0 | xargs -i -0 chmod 0644 '{}' 復制代碼

繼續討論mp3文件。

如果當前路徑有些mp3文件只是鏈接、而原始文件在其他地方,find會默認忽略。

為了將這些非當前路徑的mp3也包括進來,可以加個謂語-follow(跟蹤)

$ find . -follow -name '*.mp3' -print0 | xargs -i -0 mv '{}' ~/songs 復制代碼

如果mp3后綴名可能是大寫:MP3,可以使用-iname(ignore case)忽略大小寫

$ find . -follow -iname '*.mp3' -print0 | xargs -i -0 mv '{}' ~/songs 復制代碼

find不支持-iname? 那就只能這樣寫了:

$ find . -follow -name '*.[Mm][Pp]3' -print0 | xargs -i -0 mv '{}' ~/songs 復制代碼

也可以按修改時間(modification time)查找。+大于,-小于,無符號表示“正好”

大于90天

$ find . -name '*.jpg' -mtime +90 -print 復制代碼

還可以用邏輯與-a(and)、或-o(or)做組合搜索

大于7天,并且,小于14天

$ find . -mtime +7 -a -mtime -14 -print 復制代碼

大于14天的text文件,或者,小于14天的txt文件

$ find . -mtime +14 -name '*.text' -o \( -mtime -14 -name '*.txt' \) -print 復制代碼

上邊的一對圓括號是必須的,因為相鄰的兩個謂語-name和-print,等效于一個邏輯與-a的組合。

所以,為了消除歧義,必須加上括號。且因為括號在bash語法中有其他特殊含義,所以還必要用反斜杠\取消轉義。

也可以先指定文件類型,縮小查找的范圍

查找文件名中包含關鍵字python的目錄

$ find . -type d -name '*python*' -print 復制代碼

文件類型【簡表】

符號描述
b塊文件
c字符文件
d目錄
p管道文件,fifo
f普通文件
l符號鏈接
s套接字
DSolaris專用,“門”

-size表示按文件大小查找。加減號用法同-mtime

大于3M

$ find . -size +3000k -print 復制代碼

文件大小的單位,除了k,也可以是c,表示字節。b或者留空,表示塊(block),一個塊通常是512字節,根據不同的文件系統而定。

如果只依稀記得要找的文件中包含某個特殊的詞,比如'basher',且該文件是txt文本,也確信就在當前路徑,那么可以這樣,直接用grep

grep -i basher *.txt 復制代碼

開關**-i**表示忽略大小寫(ignore case)

如果文件可能藏在當前路徑的某個子目錄下,可以用*通配符

grep -i basher */*.txt 復制代碼

如果不靈,那就該find命令上場了

find . -name '*.txt' -exec grep -Hi basher '{}' \; 復制代碼

其中的-H顯示文件名。最后的\;表示該組命令結束,不加也行,這里只是預防如果你在后邊還要跟些其他語句。其他的命令參數,在前邊都介紹過了。

如果搜索范圍過大,find的效率是很低的,因為它對整個查找范圍用的窮舉搜索。

locate命令,速度就快很多。因為它的搜索,建立在索引上。

當然,前提是索引存在,且是新的。

這一點,操作系統會做索引的維護工作,比如通過cron job來完成

索引的內容,一般是整個文件系統內各文件的名稱和位置,不會深入到文件內部,所以不支持按內容搜索。

slocate, 除了提供文件名和路徑信息,還包含權限。用戶只能搜索到自己名下的文件。出于安全考慮,locate命令一般會鏈接到slocate。

tar gzip 壓縮 解壓

AR, ARC, ARJ, BIN, BZ2, CAB, CAB, JAR, CPIO, DEB, HQX, LHA, LZH, RAR, RPM, UUE, ZOO

在傳統的UNIX語境中,存檔、打包(archiving, combining),和壓縮(compressing)是兩碼事,對應不同的工具。而在windows,是不做區分的。

首先,tar(tape archive)生成包(tarball),然后,再通過gzip, bzip2等工具生成類似tarball.tar.Z, tarball.tar.gz, tarball.tgz, or tarball.tar.bz2等格式的壓縮包,當然也包括流行的zip格式。

tarball.tar.Z是最原始的UNIX壓縮包格式。現在更常見的,是gzip,bzip2等。

tarball.tar.gz是這樣生成的:

$ tar cf 包.tar 要打包的文件路徑 # 打包 $ gzip 包.tar # 壓縮 復制代碼

GNU tar兼備壓縮的功能,可以一步到位

$ tar czf 包.tgz 要打包的文件路徑 復制代碼

為了與windows兼容,也經常會使用zip格式

$ zip -r 壓縮包 要打包的文件路徑 復制代碼

zip和unzip由InfoZip提供,用于大多數UNIX平臺。**-l開關用于UNIX的換行符向DOS兼容,-ll**用于反向兼容。更多細節,請參考使用手冊。

紅帽的RPM(Red Hat Package Manager),其實是加了頭部的CPIO文件

$ rpm2cpio some.rpm | cpio -i 復制代碼

Debian的.deb文件,其實是gzipped或bzipped格式的ar包,可以通過標準的ar, gunzip或bunzip2來解包。

windows平臺的WinZip, PKZIP, FilZip,以及7-Zip等,也支持眾多的壓縮格式。

解壓之前,最好先用file命令查看壓縮包的格式,再決定使用哪種解壓工具

$ file 文件.* 文件.1: GNU tar archive 文件.2: gzip compressed data, from Unix $ $ gunzip 文件.2 gunzip: 文件.2: unknown suffix -- ignored $ $ mv 文件.2 文件.2.gz $ gunzip 文件.2.gz $ $ file 文件.2 文件.2: GNU tar archive 復制代碼

拆包之前,最好先用tar -t查看路徑表。預先了解拆包時各個文件的去向

$ tar tf some.tar | awk -F/ '{print $1}' | sort -u 復制代碼

最后,強烈建議,每次用tar打包時,使用相對路徑,而不是絕對路徑。這樣,拆包時文件的去向是可控的。用絕對路徑的話,會有覆蓋掉該路徑下原始文件的風險。

五、加分技能

daemon-ize 守護進程

守護進程,沒有控制臺,但常駐后臺

首先,守護進程(daemon)不是像這樣,用個&就能簡單實現的

$ ./daemonscript.sh & 復制代碼

特別是當你用SSH遠程操作時。如果此時你登出,SSH還會一直掛在那,傻等那個“后臺”腳本結束。而那個腳本,是不會結束的。

正解一:

$ nohup ./daemonscript.sh 0<&- 1>/dev/null 2>&1 & 復制代碼

分解來看:

nohup # 告訴腳本,不接收控制臺登出時傳過來的hangup信號 ./daemonscript.sh # 腳本名稱 0<&- # 關閉STDIN(0) 1>/dev/null # 丟棄STDOUT(1) 2>&1 # 丟棄STDERR(2) & # 后臺運行 復制代碼

正解二:

nohup mydaemonscript >>/var/log/myadmin.log 2>&1 <&- & 復制代碼

分解來看:

nohup ./daemonscript.sh >>/var/log/some.log # STDOUT(1) 追加寫進日志 2>&1 # STDERR(2) 追加寫進日志 <&- # 關閉 STDIN(0) & 復制代碼

注意一個細節: 在正解二中,對于標準文件描述符(0,1,2),0和1的符號可以省略不寫,2必須顯式聲明。關于順序,1必須在2之前聲明。0的位置隨意。

source . $include 代碼復用

先看一個配置文件,這里定義了三個參數

$ cat myprefs.cfg SCRATCH_DIR=/var/tmp IMG_FMT=png SND_FMT=ogg $ 復制代碼

對于通用的參數設置,或代碼片段,可以放在單獨的文件中,供其他腳本復用。

復用的方法有三種,逐一介紹:

方法一,使用bash的source命令

source $HOME/myprefs.cfg cd ${SCRATCH_DIR:-/tmp} echo 你常用的圖片格式是:$IMG_FMT echo 你常用的音樂格式是:$SND_FMT 復制代碼

方法二,POSIX風格的單點號.

. $HOME/myprefs.cfg 復制代碼

點號很容易被漏看

方法三,類c語言

$include $HOME/myprefs.cfg 復制代碼

注意:include前邊的$不是命令提示符。并且,請保證被復用的文件可讀、可執行。

代碼復用,是bash腳本一個既強大又危險的功能。因為它讓你少寫代碼的同時,也讓你的腳本,對外部代碼敞開了大門。

開頭那個配置文件,只是一些常量聲明。也叫做被動語句

主動語句呢?

$ cat myprefs.cfg SCRATCH_DIR=/var/tmp IMG_FMT=$(cat $HOME/myimage.pref) if [ -e /media/mp3 ] thenSND_FMT=mp3 elseSND_FMT=ogg fi echo config file loaded $ 復制代碼

看到沒?邏輯判斷,cat命令,echo命令

只要符合語法規范就行,任意發揮。

function 函數的定義、傳值、返回

函數請務必在使用前定義,否則會收到類似command not found的錯誤提示。

先定義

function usage ( ) {printf "usage: %s [ -a | - b ] file1 ... filen\n" $0 > &2 } 復制代碼

后使用

if [ $# -lt 1] thenusage fi 復制代碼

定義的方式可以很靈活,

function usage ( ) {... }function usage {... }usage ( ) {... }usage ( ) {... } 復制代碼

以上四種寫法都對。不過,關鍵字function或(),至少要保留一樣。

建議保留function,既一目了然,也方便像這樣grep '^function' script進行函數查找

重定向也可以放在外邊,把整個函數的輸出都傳給STDERR(2)

function usage ( ) {printf "usage: %s [ -a | - b ] file1 ... filen\n" $0 } > &2 復制代碼

如何傳參給函數?如何使用返回的結果?

# 定義函數: function max ( ) { ... }# 傳參給函數: max 128 $SIM max $VAR $CNT 復制代碼

參數緊跟在函數名后,用空格隔開,不需要像其他語言那樣使用括號

函數的返回呢?

先看完整的函數定義:

function max ( ) {local HIDNif [ $1 -gt $2 ]thenBIGR=$1elseBIGR=$2fiHIDN=5 } 復制代碼

執行結果保存在(非局部變量)BIGR中。HIDN是局部變量

所以,可以像這樣在函數外部訪問返回值

echo $BIGR 復制代碼

或者,也可以讓函數把返回值先吐到屏幕

function max ( ) {if [ $1 -gt $2 ]thenecho $1elseecho $2fi } 復制代碼

然后,在外部用$()接收屏幕的內容

BIGR=$(max 128 $SIM) 復制代碼

第一種方法通過變量綁定了返回結果(coupling),比較呆板。

后一種解除了綁定(de-coupling),如果返回值很多的話,從屏幕接收完還需要多一步拆解的動作,又會比較麻煩。

孰優孰劣,自己權衡吧

在函數的生命周期,可訪問到一個叫$FUNCNAME的內置變量,這個變量以數組的形式,保存當前函數調用棧(call stack)的所有信息。[0]是函數名自身,[1], [2]...是各個參數。棧頂是main。

捕獲中斷 trap kill

中斷信號是獵物,trap是陷阱

trap -l和kill -l可以列出(list)所有中斷,種類和數量因各操作系統而異。

$ trap -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 ... 復制代碼

腳本被中斷時,返回值(exit status)是128+中斷編號。比如,ABRT對應134(128+6)。

特別值得一提的,是-SIGKILL ( -9 ),它不會被任何陷阱捕獲,相當于絕殺。腳本會被KILL信號即刻殺死,不會做任何退出前的現場清理工作。所以,請謹慎使用。

此外,還有三個偽中斷信號沒有列出。主要用在一些特定的場合。做個簡單介紹,更多細節請參考bash手冊。

  • DEBUG,調試信號,類似于EXIT,通常放在待調試的語句之前。
  • RETURN,返回信號,在函數調用或外部source (.)引用完成時,主語句恢復執行時觸發。
  • ERR,異常信號,在某條命令崩潰時觸發。

trap命令的基本用法

trap [-lp] 參數 中斷信號 復制代碼

-l前邊介紹過了。-p打印當前設置的陷阱和它們的句柄(handlers)。

參數是一條自定義的語句或函數。中斷可以是一條或多條。

trap ' echo "你逮到我了! $?" ' ABRT EXIT HUP INT TERM QUIT 復制代碼

參數也可以是空字符串(null string),表示忽略后邊列出的中斷。

參數也可以留空,或只寫一個減號-,表示按系統缺省處理。

trap USR1 trap - USR1 復制代碼

POSIX,這里具體指的是1003.2標準,會對trap和kill的行為產生一些影響。

做個小實驗:

$ set -o posix # 首先,打開posix開關$ kill -l HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 ... 復制代碼

可以發現,所有中斷標識的前綴SIG和編號,都消失了。

而且,posix對缺省參數,有更嚴格的格式要求:

$ trap USR1 # 格式錯誤。參數不能為空 trap: usage: trap [-lp] [[arg] signal_spec ...]$ trap - USR1 # 格式正確。留空的話,至少要寫個減號占位$ set +o posix # 關閉posix開關 復制代碼

POSIX風格這種顯式的表達形式,有助于消除語法歧義。

最后,來看個完整的例子:一個殺不死的腳本。

#!/bin/bash - #名稱:hard_to_kill.sh# 先定義一個陷阱函數 function trapped {if [ "$1" = "USR1" ]; thenecho "[$?] 被你的$1陷阱逮到了!"exitelseecho "[$?] 逃過$1陷阱,滅哈哈哈~~~"fi }# 設置哪些中斷信號需要捕獲 trap "trapped ABRT" ABRT trap "trapped EXIT" EXIT trap "trapped HUP" HUP trap "trapped INT" INT trap "trapped KILL" KILL # 如前所述,這條語句是無效的 trap "trapped QUIT" QUIT trap "trapped TERM" TERM trap "trapped USR1" USR1 # 能殺死腳本的,只有USR1# 陷阱設置完畢,開個無限循環 while ((1)); do: done 復制代碼

這個腳本比較有趣,讀者可以在自己機子上跑跑。通過發送kill -USR1信號或萬能的-KILL信號退出。

關于別名 alias

在bash交互控制臺,你可以通過alias來設置各種命令的別名。

alias很聰明,能避免定義出現死循環

$ alias ls='ls -a' $ alias echo='echo ~~~' 復制代碼

不帶參數時,列出所有。有些默認的別名,預定義在bash的配置文件中(比如~/.bashrc)

$ alias grep='grep --color=auto' l='ls -CF' la='ls -A' ll='ls -l' ls='ls --color=auto' 復制代碼

alias的實現機制,就是簡單的文本替換,且具有高優先級。

比如,用一個字母h,列出主目錄的文件

$ alias h='ls $HOME'#或者 $ alias h='ls ~' 復制代碼

這里注意要用單引號,表示$HOME變量在使用時才展開,而不是定義時。

unalias用于解除別名

$ unalias h 復制代碼

如果別名多到你自己都看不懂,可以用-a,刪除當前會話的所有(all)別名

$ unalias -a 復制代碼

但如果unalias自身也是個別名,怎么辦?

可以用反斜杠\,禁止對別名(如果存在)的展開

$ \unalias -a 復制代碼

另外,不能像這樣使用位置變量$1,因為它在當前語境沒有任何意義,除非是放在一個函數里邊。$HOME不一樣,它是環境變量。

$ alias dr='mkdir $1 && cd $1' 復制代碼

如果語境中存在重名,但只想使用原生的bash內置命令,可以用builtin修飾:

# builtin 命令 命令的參數 $ builtin echo test 復制代碼

內置,是指寫在bash源碼里邊,隨之啟動并常駐內存的那些最基本的命令,cd, exit等。

原生,與用戶自定義相對。

如果語境中存在重名,但只想使用原生的外部命令,可以用command修飾:

# command 命令 命令的參數 $ command awk -f asar.awk 復制代碼

外部,一般體型較大,不隨bash啟動,在使用時才從硬盤調入內存的命令,grep, awk等。

如果搞不清楚哪些對哪些,可以先用type(-a)查看命令的類別

# exit是原生的內置命令 $ type exit exit is a shell builtin# ls既是自定義別名,也是外部命令 $ type -a ls ls is aliased to 'ls --color=auto' ls is /bin/ls 復制代碼

對于外部命令,你也可以添加絕對路徑前綴來繞過別名

$ /bin/ls 復制代碼

前提是你得知道準確的路徑。否則,還是用command好了,它會從$PATH中讀取路徑。當然,如果路徑不對,command也枉然。

最后再看個例子。這里,用戶自定義了一個同名的cd函數:用三個點號...替代常規的寫法../..,返回上上級目錄。在函數內部,就使用了builtin,來引用重名的內置命令cd

function cd () {if [[ $1 = "..." ]]thenbuiltin cd ../..elsebuiltin cd $1fi } 復制代碼

六、日期與時間

strftime格式【全表】

格式描述
%%百分號,字面
%a星期,簡 (Sun..Sat)
%A星期,全 (Sunday..Saturday)
%B月,全 (January..December)
%b月,簡 %h(MMM Jan..Dec)
%c日期 時間,本地缺省
%C年,兩位 (CC 00..99)
%d天,兩位 (DD 01..31)
%D日期 %m/%d/%y (MM/DD/YY) 【注1】
%e天 (D 1..31)
%F日期 %Y-%m-%d (CCYY-MM-DD) 【注2】
%g年,兩位,對應%V周數 (YY)
%G年,四位,對應%V周數 (CCYY)
%H小時,全天,兩位 (HH 00..23)
%h月,簡 %b (MMM Jan..Dec)
%I小時,半天,兩位 (hh 01..12)
%j天,三位 (001..366)
%k小時,全天 (H 0..23)
%l小時,半天 (h 1..12)
%m月,兩位 (MM 01..12)
%M分,兩位 (MM 00..59)
%n新行,字面
%N納秒,九位 (000000000..999999999) [GNU]
%p半天,大寫 (AM/PM)
%P半天,小寫 (am/pm) [GNU]
%r時間,半天 %I:%M:%S (hh:MM:SS AM/PM)
%R小時:分,兩位 %H:%M(HH:MM)
%s秒數,UTC元時間(1970年1月1日零時)至今
%S秒,兩位 (SS 00..61) 【注3】
%t制表符,字面
%T時間 %H:%M:%S (HH:MM:SS)
%u周一-周日 (1..7)
%U周數 (周日-周六) (00..53)
%v日期,非標準 %e-%b-%Y (D-MMM-CCYY)
%V周數 (周日-周六) (01..53) 【注4】
%w周日-周六 (0..6)
%W周數 (周一-周日) (00..53)
%x日期,本地最優
%X時間,本地最優
%y年,兩位 (YY 00..99)
%Y年,四位 CCYY
%zUTC時區 ISO 8601格式 [-]hhmm
%Z時區名稱

【注1】只有美國才用MM/DD/YY。其他地方都是DD/MM/YY,所以這個格式有歧義,應避免使用。建議用%F替代,因為它是公認的標準格式,且表達清晰。

【注2】CCYY-MM-DD符合ISO 8601標準; HP-UX系統是個例外,它的月份用英文全稱表示。

【注3】秒數的區間之所以是00-61,而不是00-59,是考慮到存在周期性的閏秒和雙閏秒。

【注4】根據ISO 8601,包含1月1日的星期,如果它在新年的天數至少有四天,則被視為新年的第一周,否則被歸為上一年的第53周。而它的下一周則是新年的第一周。對應的年份通過%G獲得。

格式化時間

先聲明幾個環境變量

$ STRICT_ISO_8601='%Y-%m-%dT%H:%M:%S%z' # 【注1】 $ ISO_8601='%Y-%m-%d %H:%M:%S %Z' # 可讀性更強的ISO-8601 $ ISO_8601_1='%Y-%m-%d %T %Z' # %T等于%H:%M:%S $ DATEFILE='%Y%m%d%H%M%S' # 用于在文件名內嵌入時間戳 復制代碼

【注1】: ISO

ISO 8601的優點:

  • 使用廣泛,歧義少
  • 更易讀,且便于awk和cut做切割處理
  • 不論是用于文件名或時間序列,都能正確排序

加號+雖然可以放在變量聲明里,但因為有些系統對這個加號的位置比較挑剔,所以還是建議在每次用到變量時才顯式加入。

$ date "+$ISO_8601" 2018-01-21 01:16:53 EST 復制代碼

GNU awk可以直接使用strftime函數

$ gawk "BEGIN {print strftime(\"$ISO_8601\")}" 2018-01-21 01:21:14 EST 復制代碼

小寫的%z不是標準的時區寫法,用于GNU date命令。因系統而異。

$ date "+$STRICT_ISO_8601" 2018-01-21T01:21:54-0500 復制代碼

GNU date支持-d參數,用于指定任意的時間。不是每個版本都支持。

$ date -d '2034-01-21' "+$ISO_8601" 2034-01-21 00:00:00 EST 復制代碼

MM/DD/YY、DD/MM/YY、M/D/YY或D/M/YY都是帶有歧義的日期格式,不建議使用。

$ date "+程序啟動于: $ISO_8601" 程序啟動于: 2018-01-21 01:28:14 EST 復制代碼

二十四小時制比十二小時制表述更清晰,也便于做時間切割

$ printf "%b" "程序啟動于: $(date "+$ISO_8601")\n" 程序啟動于: 2018-01-21 01:29:20 EST 復制代碼

在文件名內嵌入時間戳

$ echo "可以這樣重命名文件: mv file.log file_$(date +$DATEFILE).log" 可以這樣重命名文件: mv file.log file_20180121012750.log 復制代碼

時區,閏年以及夏令時等的轉換,是個及其復雜的話題和技術活,不建議讀者自行操作,而應該交給相關的命令或工具去做。

格式化任意時間

-d參數可以通過字符串形式,指定任意時間,功能異常強大。

$ date '+%Y-%m-%d %H:%M:%S %z' 2018-01-21 02:25:47 -0500$ date -d 'today' '+%Y-%m-%d %H:%M:%S %z' 2018-01-21 02:26:05 -0500$ date -d 'yesterday' '+%Y-%m-%d %H:%M:%S %z' 2018-01-20 02:26:32 -0500$ date -d 'tomorrow' '+%Y-%m-%d %H:%M:%S %z' 2018-01-22 02:26:56 -0500$ date -d 'Monday' '+%Y-%m-%d %H:%M:%S %z' 2018-01-22 00:00:00 -0500$ date -d 'this Monday' '+%Y-%m-%d %H:%M:%S %z' 2018-01-22 00:00:00 -0500$ date -d 'last Monday' '+%Y-%m-%d %H:%M:%S %z' 2018-01-15 00:00:00 -0500$ date -d 'next Monday' '+%Y-%m-%d %H:%M:%S %z' 2018-01-22 00:00:00 -0500$ date -d 'last week' '+%Y-%m-%d %H:%M:%S %z' 2018-01-14 02:29:12 -0500$ date -d 'next week' '+%Y-%m-%d %H:%M:%S %z' 2018-01-28 02:29:35 -0500$ date -d '2 weeks' '+%Y-%m-%d %H:%M:%S %z' 2018-02-04 02:30:03 -0500$ date -d '-2 weeks' '+%Y-%m-%d %H:%M:%S %z' 2018-01-07 02:30:35 -0500$ date -d '2 weeks ago' '+%Y-%m-%d %H:%M:%S %z' 2018-01-07 02:30:59 -0500$ date -d '+4 days' '+%Y-%m-%d %H:%M:%S %z' 2018-01-25 02:31:24 -0500$ date -d '-6 days' '+%Y-%m-%d %H:%M:%S %z' 2018-01-15 02:31:32 -0500$ date -d '2000-01-01 +12 days' '+%Y-%m-%d %H:%M:%S %z' 2000-01-13 00:00:00 -0500$ date -d '3 months 1 day' '+%Y-%m-%d %H:%M:%S %z' 2018-04-22 03:32:40 -0400 復制代碼

設置默認時間【腳本】

看個完整的例子。用于自定義生成跨度一周的日期區間。可傳給SQL做查詢,生成定期匯報等。

#!/usr/bin/env bash # 使用正午時間,是為了避免如果腳本在午夜運行,多幾秒就會使得多算一天的錯誤 START_DATE=$(date -d 'last week Monday 12:00:00' '+%Y-%m-%d')while [ 1 ]; doprintf "%b" "開始日期:$START_DATE, 是否正確? (Y/新日期) "read answer# ENTER, "Y" or "y"以外的輸入被視為待驗證日期# 日期格式: CCYY-MM-DDcase "$answer" in[Yy]) break;;[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])START_DATE="$answer"printf "%b" "用$answer覆寫$START_DATE [ok]\n";;*)printf "%b" "日期格式有誤,請重試\n";;esac doneEND_DATE=$(date -d "$START_DATE +7 days" '+%Y-%m-%d')echo "START_DATE: $START_DATE" echo "END_DATE: $END_DATE" 復制代碼

cron時間設置【腳本】

cron用于執行定時的計劃任務。下面是些簡單的時間設置。

# Vixie Cron # 分 時 天 月 星期天 # 0-59 0-23 1-31 1-12 0-7# 第一個星期三 @ 23:00 00 23 1-7 * Wed [ "$(date '+%a')" == "Wed" ] && 命令 參數# 第二個星期四 @ 23:00 00 23 8-14 * Thu [ "$(date '+%a')" == "Thu" ] && 命令# 第三個星期五 @ 23:00 00 23 15-21 * Fri [ "$(date '+%a')" == "Fri" ] && 命令# 第四個星期六 @ 23:00 00 23 22-27 * Sat [ "$(date '+%a')" == "Sat" ] && 命令# 第五個星期日 @ 23:00 00 23 28-31 * Sun [ "$(date '+%a')" == "Sun" ] && 命令 復制代碼

要注意的是,每個月的最后一周不一定是滿的,如下表所示。

一月 2018

日一二三四五六
123456
78910111213
14151617181920
21222324252627
28293031

所以如果你指定了第五個星期五,一定要知道自己在做什么。

epoch 元秒

表達及轉換

基本概念

  • 元時 epoch: 1970年1月1日零時零分零秒,1970-01-01T00:00:00
  • 元秒 epoch seconds: 是從元時至今的總秒數。

“現在”的元秒表示

$ date '+%s' 1516522891 復制代碼

任意時間點

$ date -d '2034-01-21 12:00:00 +0000' '+%s' 2021457600 復制代碼

將元秒轉換為可讀的形式

$ EPOCH='1516522891'$ date -d "1970-01-01 UTC $EPOCH seconds" +"%Y-%m-%d %T %z" 2018-01-21 03:21:31 -0500$ date --utc --date "1970-01-01 $EPOCH seconds" +"%Y-%m-%d %T %z" 2018-01-21 08:21:31 +0000 復制代碼

運算

下邊這個元秒運算的例子簡單易懂。

CORRECTION='172800' # 修正值設為兩天# 。。獲取bad_date的代碼。。bad_date='Jan 2 05:13:05' # 系統日志的時間格式# 先轉換為元秒 bad_epoch=$(date -d "$bad_date" '+%s')# 修正 good_epoch=$(( bad_epoch + $CORRECTION ))# 再轉換為可讀形式 good_date=$(date -d "1970-01-01 UTC $good_epoch seconds")# ISO格式 good_date_iso=$(date -d "1970-01-01 UTC $good_epoch seconds" +'%Y-%m-%d %T')echo "錯誤日期: $bad_date" echo "錯誤元秒: $bad_epoch" echo "修正: +$CORRECTION" echo "正確元秒: $good_epoch" echo "正確日期: $good_date" echo "正確日期_iso: $good_date_iso"# 。。good_date用于后續代碼。。 復制代碼

元秒換算 【全表】

秒分時天
601
3005
60010
3,600601
18,0003005
36,00060010
86,4001,440241
172,8002,880482
604,80010,0801687
1,209,60020,16033614
2,592,00043,20072030
31,536,000525,6008,760365

轉載于:https://juejin.im/post/5a75e8fc5188257a5c60424a

總結

以上是生活随笔為你收集整理的Bash Cookbook 学习笔记 【中级】的全部內容,希望文章能夠幫你解決所遇到的問題。

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