Read Me
- 本文是以英文版<bash cookbook> 为基础整理的笔记,力求脱水
- 2018.01.21 更新完【中级】。内容包括工具、函数、中断及时间处理等进阶主题。
-
本系列其他两篇,与之互为参考
-
所有代码在本机测试通过
- 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 we
printf("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匹配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 { # 触发计数语句块的逻辑,即该行的项数要大于7
user[$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 scaling
maxm = 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
本身就是一个庞大的话题,所以原著<Bash Cookbook>并未过多涉及。
最近看<Linux命令速查手册>(第2版)的时候,发现书中引用的一个链接内容不错,中文翻得也不错。
所以放在此处,供有求知欲的读者参考。
并特此感谢原作者Eric Pement,及译者Joe Hong。
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 -rn
17 /bin/false # 禁止登录
16 /usr/sbin/nologin # 禁止登录
2 /bin/bash
1 /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
总结一下除了回车键之外的转义字符:
转义字符【简表】
转义符 | 描述 |
---|---|
\ooo | 1-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 | 套接字 |
D | Solaris专用,“门” |
-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 ]
then
SND_FMT=mp3
else
SND_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]
then
usage
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 HIDN
if [ $1 -gt $2 ]
then
BIGR=$1
else
BIGR=$2
fi
HIDN=5
}
执行结果保存在(非局部变量)BIGR中。HIDN是局部变量
所以,可以像这样在函数外部访问返回值
echo $BIGR
或者,也可以让函数把返回值先吐到屏幕
function max ( )
{
if [ $1 -gt $2 ]
then
echo $1
else
echo $2
fi
}
然后,在外部用$()
接收屏幕的内容
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" ]; then
echo "[$?] 被你的$1陷阱逮到了!"
exit
else
echo "[$?] 逃过$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 = "..." ]]
then
builtin cd ../..
else
builtin cd $1
fi
}
六、日期与时间
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 |
%z | UTC时区 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 ]; do
printf "%b" "开始日期:$START_DATE, 是否正确? (Y/新日期) "
read answer
# ENTER, "Y" or "y"以外的输入被视为待验证日期
# 日期格式: CCYY-MM-DD
case "$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
done
END_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
日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 |
所以如果你指定了第五个星期五,一定要知道自己在做什么。
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用于后续代码。。
元秒换算 【全表】
秒 | 分 | 时 | 天 |
---|---|---|---|
60 | 1 | ||
300 | 5 | ||
600 | 10 | ||
3,600 | 60 | 1 | |
18,000 | 300 | 5 | |
36,000 | 600 | 10 | |
86,400 | 1,440 | 24 | 1 |
172,800 | 2,880 | 48 | 2 |
604,800 | 10,080 | 168 | 7 |
1,209,600 | 20,160 | 336 | 14 |
2,592,000 | 43,200 | 720 | 30 |
31,536,000 | 525,600 | 8,760 | 365 |
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。