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 -lls -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 -lkill -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標準,會對trapkill的行為產生一些影響。

做個小實驗:

$ 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/YYDD/MM/YYM/D/YYD/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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/278467.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/278467.shtml
英文地址,請注明出處:http://en.pswp.cn/news/278467.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

設置Windows 10時如何創建本地帳戶

Windows 10 tries its hardest to make you use a Microsoft account. The option was already hidden, but now it’s not even offered on Windows 10 Home while you’re connected to the internet. Here’s how to create a local account anyway. Windows 10盡最大努力使…

HSQL

Hive的數據存儲  1、Hive中所有的數據都存儲在 HDFS 中&#xff0c;沒有專門的數據存儲格式&#xff08;可支持Text&#xff0c;SequenceFile&#xff0c;ParquetFile&#xff0c;RCFILE等&#xff09;  2、只需要在創建表的時候告訴 Hive 數據中的列分隔符和行分隔符&…

在PowerPoint 2010中將鼠標用作激光筆

Have you ever wished you had a laser pointer to focus attention on a key point in a PowerPoint slideshow? Today, we’ll take a look at how can use use your mouse as a laser pointer in PowerPoint 2010. 您是否曾經希望激光指示器能將注意力集中在PowerPoint幻燈…

Java 8 并發: 原子變量和 ConcurrentMap

原文地址: Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap AtomicInteger java.concurrent.atomic 包下有很多原子操作的類。 在有些情況下&#xff0c;原子操作可以在不使用 synchronized 關鍵字和鎖的情況下解決多線程安全問題。 在內部&#xff0c;原子類…

this表示當前對象簡單實例

直接上代碼 class Message { private Channel channel ; // 保存消息發送通道 private String title ; // 消息標題 private String content ; // 消息內容 // 4、調用此構造實例化&#xff0c;此時的channel 主類ch public Message(Channel channel,String title,String cont…

twitter推文不收錄_如何使用Twitter書簽保存推文供以后使用

twitter推文不收錄Khamosh PathakKhamosh PathakTwitter has a new Bookmarks feature that lets you privately save tweets for later. If you’ve been using the Like feature as a workaround for saving tweets, here’s why you should start bookmarking. Twitter具有一…

if的作用域問題 *輸出1~6的隨機數*

1 //測試if語句2 public class TestIf {3 public static void main(String[] args){4 double d Math.random();//0~1之間的小數5 int e (int)(d*5); //[0,4]6 //int f 1(int)(d*6); //[1,6] 擲色子7 System.out.println(e);8 …

為您的Blogger博客設計一個美麗的新主題

Would you like to give your Blogger blog a fresh coat of paint with a new theme? Here’s how you can use the new Template Designer to make your Blogger site stand out from the crowd and look great. 您想給Blogger博客一個新的主題嗎&#xff1f; 您可以通過以…

Lab 6-4

In this lab, we’ll analyze the malware found in the file Lab06-04.exe. Questions and Short Answers What is the difference between the calls made from the main method in Labs 6-3 and 6-4? A: The function at 0x401000 is the check Internet connection method…

步入三十歲前的總結:看似經歷很多得到很多,但,實際卻一無所得

本文算是一篇審視自己的文章吧&#xff0c;感覺跟我類似經歷的人應該很多&#xff0c;認同感應該也大一些。我是12年網絡專業很普通的一所大專院校畢業&#xff0c;到現在為止工作已經超過五年。這五年里&#xff0c;做過運維工程師&#xff0c;也在小車床工作間里做了一下技工…

vue---day03

1. Vue的生命周期 - 創建和銷毀的時候可以做一些我們自己的事情 - beforeCreated - created - beforeMount - mounted - beforeUpdate - updated - activated - deactivated - beforeDestroy - destroyed 1.1 知識點回顧 1.1.1 be…

U Sparkle 開發者計劃招募中!

向我們投稿吧 在此之前&#xff0c;我們有收到過幾篇民間高手的投稿&#xff0c;如&#xff1a; USequencer 初識&#xff08;作者&#xff1a;焱燚(七火)&#xff09; Unity游戲界面解決方案: PSD For UGUI&#xff08;作者&#xff1a;張俊欽&#xff09; UGUI 降低填充率技巧…

階乘和 大整數

///大整數階乘的和 #include<bits/stdc.h> using namespace std; int main() {int n;while(cin>>n){int a[2000] {1},b[2000] {0}; //存放結果的數組a。int c; //b用于存放每位存放的結果。int r0; //r用來表示進位的數。int h1,hb1; //h用來表示運算過程中 結果a…

如何添加引文標_如何在Google文檔中查找和添加引文

如何添加引文標When writing papers, you need to generate a detailed and accurate list of all the sources you’ve cited in your paper. With Google Docs, you can easily find and then add citations to all of your research papers. 撰寫論文時&#xff0c;您需要生…

mongo ttl索引

db.log_events.find() # 查找log_events里的所有數據db.log_events.createIndex( { "LogDT": 1 }, { expireAfterSeconds: 3600 } ) #設置log_events里的TTL過期索引清理時間為3600秒db.runComman…

Linux Centos下SQL Server 2017安裝和配置

Linux Centos下SQL Server 2017安裝和配置 原文:Linux Centos下SQL Server 2017安裝和配置我們知道在Linux下安裝服務有很多方式&#xff0c;最為簡單的也就是yum安裝&#xff0c;但是很多服務通過yum是無法安裝的&#xff0c;如果想使用yum安裝&#xff0c;需要指定yum安裝倉庫…

如何在Linux上使用端口敲門(以及為什么不應該這樣做)

Photographee.eu/ShutterstockPhotographee.eu/ShutterstockPort knocking is a way to secure a server by closing firewall ports—even those you know will be used. Those ports are opened on demand if—and only if—the connection request provides the secret knoc…

小到年貨大到產業,劉村長的扶貧模式有點厲害!

河北省阜平縣平石頭村的村民&#xff0c;今年春節再也不用頭疼買什么年貨&#xff0c;去哪買年貨的問題了&#xff0c;因為他們的“村長”劉強東&#xff0c;給每戶人家都送來了年貨大禮包&#xff01;大禮包里不僅有牛奶、果汁、毛衣、長褲、波司登羽絨服、枕頭、毛巾、炊大皇…

java - 匿名類

匿名內部類 概念&#xff1a;即內部類的簡化寫法 前提&#xff1a;存在一個類&#xff08;可以是具體類也可以是抽象類&#xff09;或接口 格式&#xff1a;new 類名或接口名{重寫的方法} 本質&#xff1a;創建的是繼承了類或實現了接口的子類匿名對 象。 匿名類總是final&…

leetcode 342. Power of Four

沒想出來不用循環的。記錄下。 如果是2的次方&#xff0c;必有num & (nums - 1) bool isPowerOfFour(int num) {if (num < 1) return false;if (num & (num - 1)) return false; // 排除不是2的倍數if (num & 0x55555555) return true; // 排除不是4的倍數&am…