Linuxshell学习笔记(7)

正则表达式

感谢Linux提升篇-正则表达式-CSDN博客

基本正则表达式(BRE)集合 

符号含义
^^a,匹配以a开头的行
$x$,匹配以x结尾的行
^$匹配空行
.匹配任意一个且只有一个字符
\转义字符
*匹配前一个字符0次或1次以上
.*匹配所有内容
^.*匹配多个字符开头的所有内容
.*$匹配以多个字符结尾的内容
[abc]匹配集合内的任意一个字符
[^abc]表示对[abc]的取反

扩展正则表达式

扩展正则表达式符号在基础正则表达式的基础上增加了5个,分别是:+ ? | {} ()

符号含义
+匹配前一个字符一次或多次
[abc]+匹配方括号内的a或b或c一次或多次
?匹配前一个字符串0次或1次
|表示或者,同时过滤多个字符串
()分组过滤,被括起来的内容表示一个整体
a{n,m}匹配前一个字符最少n次,最多m次
a{n}匹配前一个字符串正好n次
a{n,}匹配前一个字符串最少n次
a{,m}匹配前一个字符串最多m次

[[-]]-的用法

比较字符串

最常用的功能之一是比较字符串,这也是我们一直在用的功能。

# 匹配
% [[ abc == abc ]] && echo good
good

# = 和 == 是一样的,最好统一使用一种
% [[ abc = abc ]] && echo good
good

# 不匹配
% [[ abc != abd ]] && echo good
good

# 正则表达式匹配 一个.代表一个随机字符
% [[ abc =~ a.c ]] && echo good
good

# 前者字符序比后者小
% [[ abc < bcd ]] && echo good
good

# 前者字符序比后者大
% [[ cde > bcd ]] && echo good
good

# 没有 >= 和 <=
% [[ cde >= bcd ]] && echo good
zsh: parse error near `bcd'

注意:有>、<、=,但没有没有 >= 和 <=! 

除了在里边用等号、不等号之类比较外,还可以判断字符串是否为空:

% str=achg
# 判断字符串内容长度是否大于 0,等同于 (($#str))
% [[ -n $str ]] && echo good
good

% str=""
# 判断字符串是否为空,等同于 ((! $#str))
% [[ -z $str ]] && echo good
good

 记:-n 不为空;-z 为空。

判断文件

另一类很重要的功能是判断文件,比如判断某一个文件是否存在、是否是目录、是否可读等等。

判断 /usr/bin/env zsh 文件是否存在:

% [[ -e /usr/bin/zsh ]] && echo good
good
% [[ -e /usr/bin/zshh ]] && echo good

-e 可以替换成如下的选项,用法是一致的:

选项符合条件的文件
-b块设备文件
-c字符设备文件
-d目录
-e存在的任何文件
-f普通文件,含符号链接,不含目录、设备文件、socket、FIFO
-g设置了 setgid 的文件
-h符号链接
-k设置了粘滞位(sticky bit)的文件
-pFIFO 文件
-r对当前进程可读的文件
-s非空文件
-u设置了 setuid 的文件
-x对当前进程可执行的文件
-w对当前进程可写的文件
-L符号链接(同 -h)
-O被当前进程的用户拥有的文件
-G被当前进程的用户组拥有的文件
-Ssocket 文件
-Natime 和 mtime 一样的文件

还有一个比较特殊的 -t 选项:

# $$ 是当前的进程 id
% ls /proc/$$/fd
0  1  10  11  2
% [[ -t 10 ]] && echo good
good
% [[ -t 3 ]] && echo good

-t 后要接数字(如果不是,相当于 0),判断当前进程是否打开了对应的 fd(进程默认会打开 0、1、2 这三个 fd,分别对应标准输入、标准输出和错误输出,此外每打开一个文件、管道或者网络连接,都会对应一个 fd,关掉后对应 fd 会消失)。

比较文件

除了判断单个文件是否符合条件外, 还可以用来比较两个文件。

# file1 比 file2 新
% [[ file1 -nt file2 ]]

# file1 比 file2 旧
% [[ file1 -ot file2 ]]

# file1 和 file2 是否对应同一个文件(路径相同或者互为硬连接)
% [[ file1 -ef file2 ]]

比较数值

也可以用来比较数值,注意不是用等号、大于号、小于号等等比较,有一系列专门的符号。通常我们没必要用来比较数值,用 (( )) 更方便一些。

# -eq 是判断两个数值是否相等
% [[ 12 -eq 12 ]] && echo good
good

-eq 可以替换成下列符号,用法一样:

符号含义
-eq相等
-ne不相等
-lt<
-gt>
-le<=
-ge>=

 

组合使用

# && 是逻辑与
% [[ a == a && b == b ]] && echo good
good

# || 是逻辑或
%  [[ a == a || a == b ]] && echo good
good

# ! 是逻辑非
% [[ ! a == b ]] && echo good
good

# 可以一起用,! 优先级最高,其次 &&,再次 ||
% [[ ! a == b && b == a || b == b ]] && echo good
good

# 如果不确定优先级,可以加小括号
% [[ ((! a == b) && b == a) || b == b ]] && echo good
good

需要注意一下空格, 内侧和内容之间需要空格隔开,== 两边也需要空格。如果是在 zsh 中直接敲入,! 后边也要加一个空格,不然会被解析成历史命令。

管道和重定向

这是和其他进程、文件系统等交互的基础。 

管道

管道是类 Unix 系统中的一个比较基础也特别重要的概念,它用于将一个程序的输出作为另一个程序的输入,进而两个程序的数据可以互通

管道的基本用法:

% ls
git  tmp
# wc -l 功能是计算输入内容的行数
% ls | wc -l
2

| 即管道,如果只输入 wc -l,wc 会等待用户输入,这时可以输入字符串,然后回车继续输入,直到按 ctrl + d 结束输入。然后 wc 会统计用户一共输入了多少行,然后输出行数

# 敲 wc -l 回车后,依次按 a 回车 b 回车 ctrl + d
% wc -l
a
b
2

但如果前边有个管道符号,ls | wc -l,那么 wc 就不等待用户输入了,而是直接将 ls 的结果作为输入读取过来,然后统计行数,输出结果

 

关于管道的更多细节

我们再运行一个简单的例子:

% cat | wc -l

# 查看 cat 进程打开的 fd
% ls -l /proc/$(pidof cat)/fd
total 0
lrwx------ 1 goreliu goreliu 0 2017-08-30 21:15 0 -> /dev/pts/1
l-wx------ 1 goreliu goreliu 0 2017-08-30 21:15 1 -> pipe:[2803]
lrwx------ 1 goreliu goreliu 0 2017-08-30 21:15 2 -> /dev/pts/1

# 查看 wc 进程打开的 fd
% ls -l /proc/$(pidof wc)/fd
total 0
lr-x------ 1 goreliu goreliu 0 2017-08-30 21:16 0 -> pipe:[2803]
lrwx------ 1 goreliu goreliu 0 2017-08-30 21:16 1 -> /dev/pts/1
lrwx------ 1 goreliu goreliu 0 2017-08-30 21:16 2 -> /dev/pts/1

cat 命令的效果是等待用户输入,等用户输入一行,它就把这行再输出来,直到用户按 ctrl + d。所以 cat | wc -l 也会等待用户输入。

我们看下 fd 的指向,/dev/ps1/1 是指向伪终端设备文件的,进程就是通过这个来读取用户的输入和输出自己的内容。0 是标准输入(即用户输入端),1 是标准输出(即正常情况的输出端),2 是错误输出(即异常情况的输出端)。但是 cat 的输出端指向了 一个管道,并且 wc 的 输入端指向了一个相同的管道,这代表两个进程的输入输出端是通过管道连接的。这种管道是匿名管道,即只在内核中存在,是没有对应的文件路径的。

重定向

重定向,指的便是 fd 的重定向,管道也是重定向的一种方法。但用得更多的是将进程的 fd 重定向到文件。

一个最简单的例子是输出内容到文件。

% echo abce > test.txt
% cat test.txt
abce

 

更多重定向的用法

一个 fd 只能重定向到一个文件,一一对应。但在 zsh 中,我们可以把一个 fd 对应到多个文件。

% cat >0.txt >1.txt >2.txt

输入完成后,3 个文件的内容都更新了, zsh 进程做了中介。

cat 的标准输出是重定向到管道了,管道对面是 zsh 进程!实际将内容写入文件的是 zsh,而不是 cat。

 

给 cat 的标准输出重定向 3 个文件,它将 3 个文件的内容全部读取了出来。

除了能同时重定向 fd 到多个文件外,还可以同时重定向到管道和文件。

 

命名管道

除了匿名管道,我们还可以使用命名管道,这样更容易控制。命名管道所使用的文件即 FIFO(First Input First Output,先入先出)文件。

# mkfifo 用来创建 FIFO 文件
% mkfifo fifo
% ls -l
prw-r--r-- 1 goreliu goreliu 0 2017-08-30 21:29 fifo|

# cat 写入 fifo
% cat > fifo

# 打开另一个 zsh,运行 wc -l 读取 fifo
% wc -l < fifo

然后在 cat 那边输入一些内容,按 ctrl + d 退出,wc 这边就会统计输入的行数。

 

exec 命令的用法

说起重定向,就不得不提 exec 命令。exec 命令主要用于启动新进程替换当前进程以及对 fd 做一些操作。

用 exec 启动新进程:

% exec cat

看上去效果和直接运行 cat 差不多。但如果运行 ctrl + d 退出 cat,终端模拟器就关闭了因为在运行 exec cat 的时候,zsh 进程将已经被 cat 取代了,回不去了

但在脚本中很少直接这样使用 exec,更多情况是用它来操作 fd:

# 将当前 zsh 的错误输出重定向到 test.txt
% exec 2>test.txt
# 随意敲入一个不存在的命令,错误提示不出现了
% fdsafds
# 错误提示被重定向到 test.txt 里
% cat test.txt
zsh: command not found: fdsafds

 

更多用法:

用法功能
n>filename重定向 fd n 的输出到 filename 文件
n<filename重定向 fd n 的输入为 filename 文件
n<>filename同时重定向 fd n 的输入输出为 filename 文件
n>&m重定向 fd n 的输出到 fd m
n<&m重定向 fd n 的输入为 fd m
n>&-关闭 fd n 的输出
n<&-关闭 fd n 的输入

更多例子:

# 把错误输出关闭,这样错误内容就不再显示
% exec 2>&-
% fsdafdsa

% exec 3>test.txt
% echo good >&3
% exec 3>&-
# 关闭后无法再输出
% echo good >&3
zsh: 3: bad file descriptor

% exec 3>test.txt
# 将 fd 4 的输出重定向到 fd 3
% exec 4>&3
% echo abcd >&4
# 输出内容到 fd 4,test.txt 内容更新了
% cat test.txt
abcd

通常情况我们用 exec 主要为了重定向输出和关闭输出,比较少操作输入。

 文件读写

写文件

写文件要比读文件简单一些,最常用的用法是使用 > 直接将命令的输出重定向到文件。如果文件存在,内容会被覆盖;如果文件不存在,会被创建。

% echo abc > test.txt

如果不想覆盖之前的文件内容,可以追加写入:

% echo abc >> test.txt

这样追加写入自动换行! 

这样如果文件存在,内容会被追加写入进去;如果文件不存在,也会被创建。

 

创建文件

有时我们只想先创建个文件,等以后需要的时候再写入。

touch 命令用于创建文件(普通文件)

% touch test1.txt test2.txt

# 或者用 echo 输出重定向,效果和 touch 一样
# 加 -n 是因为不加的话 echo 会输出一个换行符
% echo -n >>test1.txt >>test2.txt

# 或者使用输入重定向
% >>test1.txt >>test2.txt </dev/null

# mkdir 用来创建目录,如果需要在新目录创建文件
% mkdir dir1 dir2

如果文件已经存在,touch 命令会更新它的时间(mtime、ctime、atime 一起更新,其余两种方法不会)到当前时间。另外下边的清空文件方法,也都可以用来创建文件。touch 命令的使用比较方便,但如果想尽量少依赖外部命令,可以使用后两种方法

因为文件创建过程通常不存在性能瓶颈,不用过多考虑性能因素。如果需要创建大量文件,可以在自己的环境分别用这几种方法试验几次,看需要多少时间。

另外如果文件数量太多的话,方法二、三要按批次创建,因为一个进程能打开的 fd 总数是有上限的。

清空文件

有时我们需要清空一个现有的文件:

# 使用 echo 输出重定向
% echo -n >test.txt

# 使用输入重定向
% >test.txt </dev/null

# 也可以使用 truncate 命令清空文件
% truncate -s 0 test.txt

通常使用第一种方法即可,比较简单易懂。非特殊场景尽量不要用像 truncate 这样不常见的命令。

删除文件

删除文件的方法比较单一,用 rm 命令即可。

% rm test1.txt test2.txt

# -f 参数代表即使文件不存在也不报错
% rm -f test1.txt test2.txt

# -r 参数可以递归删除目录和文件
% rm -r dir1 dir2 test*.txt

# -v 参数代表 rm 会输出删除文件的过程
% rm -v test*.txt
removed 'test1.txt'
removed 'test2.txt'
多行文本写入

通常我们写文件时不会每一行都单独写入,这样效率太低。

可以先把字符串拼接起来,然后一次性写入,这样比多次写入效率更高:

% str=ab
% str+="\ncd"
% str+="\n$str"

echo $str > test.txt

可以直接把数组写入到文件,每行一个元素:

% array=(aa bb cc)

% print -l $array > test.txt

如果是将一段内容比较固定的字符串写入到文件,可以这样:

# 在脚本中也是如此,第二行以后的行首 > 代表换行,非输入内容
# <<EOF 代表遇到 EOF 时会终止输入内容
# 里边也可以使用变量
% > test.txt <<EOF
> aa
> bb
> cc dd
> ee
> EOF

% cat test.txt
aa
bb
cc dd
ee
用 mapfile 读写文件

如果不喜欢使用重定向符号,还可以用哈希表来操作文件。Zsh 有一个 zsh/mapfile 模块,用起来很方便:

% zmodload zsh/mapfile

# 这样就可以创建文件并写入内容,如果文件存在则会被覆盖
% mapfile[test.txt]="ab cd"
% cat test.txt
ab cd

# 判断文件是否存在
% (($+mapfile[test.txt])) && echo good
good

# 读取文件
% echo $mapfile[test.txt]
ab cd

# 删除文件
% unset "mapfile[test.txt]"

# 遍历文件
% for i (${(k)mapfile}) {
> echo $i
> }
test1.txt
test2.txt
从文件中间位置写入

有时我们需要从一个文件的中间位置(比如从第 100 的字符或者第三行开始)继续写入,覆盖之后的内容。Zsh 并不直接提供这样的方法,但我们可以迂回实现,先用 truncate 命令把文件截断,然后追加写。如果文件后边的内容还需要保留,可以在截断之前先读取进来(见下文读文件部分的例子),最后再写回去。

% echo 1234567890 > test.txt
# 只保留前 5 个字符
% truncate -s 5 test.txt
% cat test.txt
12345
% echo abcde >> test.txt
% cat test.txt
12345abcde

读文件

读取整个文件

读取整个文件比较容易: str=$(<test.txt)

% str=$(<test.txt)
% echo $str
aa
bb
cc dd
ee
按行遍历文件

如果文件比较大,那读取整个文件会消耗很多资源,可以按行遍历文件内容:

% while {read i} {
> echo $i
> } <test.txt
aa
bb
cc dd
ee

read 命令是从标准输入读取一行内容,把标准输入重定向后,就变成了从文件读取。

读取指定行 (f)n

如果只需要读取指定的某行或者某些行,不需要用上边的方法加自己计数。

# (f)2 是读取第二行
% echo ${"$(<test.txt)"[(f)2]}
bb
读取文件到数组

读取文件内容到数组中,每行是数组的一个元素:

% array=(${(f)"$(<test.txt)"})

 

读取指定数量的字符

有时我们需要按字节数来读取文件内容,而不是按行读取

% cat test.txt
1234567890
# -k5 是只最多读取 5 个字节,-u 0 是从 fd 0 读取,不然会卡住
% read -k 5 -u 0 str <test.txt
% echo $str
12345
  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值