1.12 通配符、正则、脚本参数和字符切片
现在开始,我们开始讲讲如何提升效率,即批量操作,这是计算机最擅长的事情,也是人类最不擅长的事。
1. 通配符是什么
通配符主要是 shell 在匹配文件名和路径时使用的。
最常见的是:
*:匹配任意长度字符?:匹配单个字符[abc]:匹配方括号中的任意一个字符[0-9]:匹配一个范围内的字符
例如:
bash
ls *.vasp # 列出当前目录下所有 .vasp 文件
cp */CONTCAR summary/ # 复制各子目录中的 CONTCAR 到 summary 目录
ls run? # 匹配 run1 run2,但不匹配 run10
ls run[12] # 匹配 run1 和 run2
ls file[0-9].txt # 匹配 file0.txt 到 file9.txt这里最重要的直觉是:
通配符通常是在匹配文件名和路径,不是在匹配文件内容。
所以:
*.txt是在匹配文件名job*是在匹配以job开头的文件或目录*/OUTCAR是在匹配各个子目录中的OUTCARrun[12]是在匹配特定字符集合
2. 还有一类常见写法:shell 展开
除了严格意义上的通配符,你还会经常看到另一类写法:
{a,b}{1..5}~
它们经常和通配符一起出现,所以新手很容易把它们统称成一类。
这样理解不算完全错,因为它们都属于 shell 帮你“展开”命令的一部分。
但更严格一点说:
*、?、[]更偏向文件名匹配{}、~更偏向 shell 自己先做展开
你现在不用死抠术语边界,但要先知道:
它们都不是在匹配文件内容,而是在命令真正执行之前,由 shell 先展开。
最常见的几个例子:
bash
echo {a,b} # 展开成 a b
echo {1..5} # 展开成 1 2 3 4 5
cp report.{pdf,txt} backup/ # 展开成两个文件名
cd ~ # 展开成当前用户家目录这里的:
{a,b}:多选展开{1..5}:范围展开~:家目录展开
{1..5} 这类写法很重要,因为后面写循环或批量命令时非常常见。
例如:
bash
mkdir run{1..5} # 一次创建 run1 到 run5
cp input run{1..5}/ # 一次展开出多个目录参数3. 通配符和 shell 展开最常见的场景
目录结构一旦统一,很多重复操作都可以一次做掉。
例如:
bash
cp *.txt backup/ # 复制所有 txt 文件
rm run*/WAVECAR # 删除各个 run 目录里的 WAVECAR
mv *.log logs/ # 移动所有 log 文件到 logs 目录
mkdir calc_{a,b,c} # 一次创建多个目录
mkdir run{1..5} # 一次创建编号目录所以通配符的价值,不是“语法炫技”,而是:
- 少打字
- 少重复
- 统一命名后批量处理很方便
4. 正则表达式又是什么
正则表达式和通配符很像,但它主要是拿来匹配文本,不是拿来匹配文件路径。
你以后最常见它的地方包括:
grepsedawk- Python 里的
re
不需要一上来学完整正则系统,先知道几个最常见的符号就够了:
.:匹配任意单个字符*:重复前一个模式 0 次或多次^:匹配行首$:匹配行尾[abc]:匹配方括号中的任意一个字符[0-9]:匹配一个数字
例如:
bash
grep '^Step' log.txt # 匹配以 Step 开头的行
grep 'error$' log.txt # 匹配以 error 结尾的行
grep 'run[0-9]' log.txt # 匹配 run1 run2 这类模式
grep 'a.*b' log.txt # 匹配 a 和 b 之间有任意内容的文本5. shell 脚本参数
除了通配符和正则,你后面还会经常看到 shell 脚本里这种写法:
bash
$1
$2
$#
$@这些不是通配符,也不是正则。
它们表示的是:你在运行脚本时传进去的参数。
例如有一个脚本 run.sh:
bash
#!/bin/bash
echo $1
echo $2如果你这样运行:
bash
bash run.sh file1 file2那么:
$1就是第一个参数file1$2就是第二个参数file2
另外 $# 表示总共传进来了多少个参数:
bash
#!/bin/bash
echo "number of args = $#"$@ 表示所有参数:
bash
#!/bin/bash
echo "$@"6. 字符串切片
后面写 shell 脚本时,你还会经常遇到一种需求:
- 从文件名里截出一部分
- 去掉某个后缀
- 拼一个新的名字
例如:
bash
name="POSCAR.vasp"
echo "${name%.vasp}" # 去掉后缀,得到 POSCAR
echo "${name%.vasp}.csv" # 去掉后缀后再拼新后缀,得到 POSCAR.csv
echo "${name}.bak" # 直接在原名字后面拼内容,得到 POSCAR.vasp.bak这里看着是不是觉得有点多此一举?哈哈是的,如果是单个文件不需要这么操作,接下来我们讲讲循环,释放真正的力量。