记录自己使用命令行时学到的技巧

find

find 命令会沿着文件层次向下遍历,匹配符合条件的文件,然后执行响应的命令。

列出某个目录下的所有文件

find ./src -print

-print 是默认的,对输出的文件名使用 \n 分隔,也可以使用 -print0 这个时候会使用 \0 作为分隔符。

使用文件名来搜索

-name 选项的参数指定了文件名中必须包含的字符串。文件名中可以包含通配符。

find ./src -name "*.js" -print

这会打印出所有在 src 目录下的 js 文件

使用 -iname,忽略大小写,其他同 -name

从 path 中进行搜索

使用 name 选项之会考虑文件名,使用 -path 选项后,会在在文件的完整路径中进行匹配

find ./src -path "*/css/*"

搜索 src 下面 css 目录中的所有文件

使用正则来搜索

使用正则表达式,根据文件的完整路径来进行搜索

find ./src -regex ".*\.md$"

寻找到所有的 markdown 文化(以 .md 结尾),注意这里的正则表达式需要完全匹配文件路径才行,而不是只匹配其中的一部分。

使用 -iregex,忽略大小写,其他同 -regex

否定参数

find ./src ! -name "*.js"

使用一个 ! 来将条件置反,以上命令搜索 src 下不是 js 的文件

所有的选项都可以使用 ! 来取反,只需要将其放在选项前面:

find . ! -name "*.js" -atime +7 ! -type f

基于目录深度

使用 -mindepth-maxdepth 来指定最小或最大的搜索深度

根据文件类型搜索

使用 -type 选项搜索指定类型的文件。这里的类型也就是 ls -l 命令打印出的文件列表的第一个字符。

-rw-r--r--   1 wangyu  staff   1857  8 21 16:58 style.css
drwxr-xr-x   4 wangyu  staff    136 11 16 21:35 typings
-rw-r--r--   1 wangyu  staff     87 11 16 21:35 typings.json
drwxr-xr-x   4 wangyu  staff    136 11 29 20:28 webpack
-rw-r--r--   1 wangyu  staff    612 12  1 15:55 yarn.lock

根据文件时间进行搜索

  • -atime:最后一次访问文件的时间
  • -mtime:最后一次修改文件的时间
  • -ctime:文件元数据最后一次被修改的时间

这些选项的参数通常使用整数指定,并带一个 -(表示小于) 或 +(表示大于)。

比如:

  • 找出七天内被访问过的文件
find . -type f -atime -7

-atime,-mtime,-ctime 都是以天为单位的,可以使用 -amin,-mmin,-cmin 来以分钟为单位进行搜索。

另外,还可以使用 -newer 选项来指定一个文件,使用找出被这个文件的修改时间更近的其他文件:

# 找出所有比 `file1.txt` 修改时间更晚的文件。
find . -newer file1.txt

基于文件大小的搜索

使用 -size 选项可以指定满足条件的文件大小。

# 找到所有小于 2k 的 js 文件
find . -size -2k -name "*.js"

除了 k 以外,还可以指定其他单位:

  • b:磁盘上的一块,512 字节
  • c:字节
  • w:字,2字节
  • k:1024 字节
  • M:1024k
  • G:1024M

删除匹配的文件

使用 -delete 选项可以删除搜索到的文件

find . -name "*.swp" -delete

基于文件权限来搜索

-perm 选项可以根据文件权限进行匹配

# 找出文件权限为 644 的文件
find . -perm 644

使用 -user 选项还可以搜索某个用户的文件

对 find 得到的结果执行命令

比如希望统计所有满足 find 条件的文件的行数,使用 -exec 选项可以轻松做到:

find . -name "*.sh" -exec wc -l {} \;

对于每一个搜索结果,这里的 {} 都会被替换为相应的文件名,而后面的 \; 则表示 exec 参数的结束,之所以需要加 \ 是为了转译,在 shell 中,; 是有特殊意义的。你也可以将 \; 换做 ';',效果一样,都是不让 shell 对它进行转译。

跳过某些目录

find . \(  -name ".git" -prune \) -o \( -type f -print \)

这里使用圆括号和 -o 分了两个组,-prune 是指去除满足该分组条件的文件,而 -print 是指打印满足该分组条件的文件。

cat

cat 用于输出一个文本中的内容,或者使用管道输出标准输出中的内容,最常见的用法如下:

cat file1.txt file2.txt

echo 'hello' | cat

cat 还有其他的一些技巧:

组合标准输出和文件的内容

要将标准输入的内容和另外一个文件的内容拼起来得到一个新的文件

ls | cat - file1.txt > file2.txt

这里的 - 是一个占位符,表示标准输出,这样标准输出的内容和 file1.txt 的内容就被组合起来添加到 file2.txt 中了。

删除多余空行

加上 -s 选项,相邻的空白行会被压缩

打印行号

-n 选项可以打印行号。-b 选项也可以打印行号,且会跳过空白行。

使用特殊字符表示制表符

-t 选项,可以将制表符打印为 ^I 便于发现这些隐藏符号。

xargs

利用管道可以轻松地将标准输出重定向至标准输入。比如:

cat main.cpp | wc -l

但是,有的时候希望将标准输出作为某个命令的参数传入。比如:

一个文件(list.txt)中有以下内容,其中包含的是一些文件名,现在希望统计这些文件的行数

ex1.sh
ex2.sh
ex3.sh

希望执行的命令应该是 wc -l ex1.sh ex2.sh ex3.sh,使用管道肯定是不能达成希望的。

这个时候就需要使用 xargs 命令了,这个命令可以轻松地将标准输入进行一些转换,然后传给一个命令作为参数,xargs 以标准输入作为数据源。因此常见的用法是结合管道使用,上面案例使用 xargs 的解法如下:

cat list.txt | xargs wc -l

这样 list.txt 中的内容,就作为 wc 命令的参数传入了。xargs 可以对传入命令的参数做更精细的控制:

格式化参数

将多行输入转换为单行输出

上面的 list.txt 中的内容是多行的,经过了 xargs 之后就变成单行的了。

$ cat list
ex1.sh
ex2.sh
ex3.sh

$ cat list | xargs
ex1.sh ex2.sh ex3.sh

将单行输入转换为多行输出

使用 -n 选项可以指定每行的最大参数数量,这里会使用默认定界符(空格)来将标准输入进行切分,然后每行显示指定数量的参数。

$ cat line.txt
1 2 3 4

$ cat line.txt | xargs -n 2
1 2
3 4

读取 stdin 将其进行格式化后传给命令

使用 -n 选择,可以指定每次传给命令的最大参数个数:

$ echo "1 2 3 4" | xargs -n 2 echo  # 每次传入最多两个参数
1 2
3 4

$ echo "1 2 3 4" | xargs -n 3 echo  # 每次传入最多 3 个参数
1 2 3
4

很多时候,一个命令还需要传入其他的一些选项,而且参数的传入顺序是固定的,这个时候可以使用占位符

$ echo "1 2 3 4" | xargs -n 1 -I {} printf "%s --> %s\n" {} {}
1 --> 1
2 --> 2
3 --> 3
4 --> 4

这里 -I 指定了占位符号 {} 后面所有的 {} 都会被参数所代替,这里的 {} 并没有什么特别意义,可以使用 -I 指定其他字符。

结合 find 使用 xargs

xargs + find 可以做很多有用的事情,但要注意别使用错误的方法

find . -type f -name "*.txt" -print | xargs rm -f

这个时候如果文件名中存在空格,比如 lib xxx.md,xargs 使用空格作为定界符,就会将它误认为是 libxxx.md,因此在 find 中要使用 -print0 来输出,在 xargs 中要使用 \0 来作为定界符。

find . -type f -name "*.txt" -print0 | xargs -0 rm -f

在 xargs 指定 -0 就可以指定其使用 \0 来作为定界符。

题外话

在 shell 中使用括号括起来的命令会在子 shell 中执行:

ls ; (cd ..; ls); ls

括号中的 cd 之后改变括号里面的语句执行时的目录,而不会影响第三个 ls 执行时候的目录。

tr

tr 命令用来转换字符,它将字符从一个集合映射到另外一个集合。其基本用法如下:

tr [options] charset1 charset2

将 charset1 中的字符转换为 charset2 中对应的字符,需要注意的是 charset1 和 charset2 的长度理论上应该相等,这样才能实现字符的一一映射。

如果 charset1 的长度大于 charset2,那么 charset2 会重复最后一个字符,直到长度相等。如果 charset2 的长度大于 charset1,那么多出来的字符会被忽略掉。

将字符串转换为大写

echo "hello world" | tr "a-z" "A-Z"

a-z, A-Z, 0-9, A-Z0-9 这都是合法的字符集

用 tr 删除字符

使用 -d 选项,可以删除集合中的字符。

$ echo 'A1B2C3' | tr -d "1-9"
ABC

使用字符补集来删除字符

使用 -c 选项,可以指定一个字符集,配合 -d 使用,会只保留集合中的字符

$ echo 'A1B2C3' | tr -d -c "A-Z"
ABC

删除重复字符

使用 -s 选项,可以指定一个字符集,tr 会将存在于该字符集中的重复字符压缩至 1 个。

$ echo 'AAA-BBB-C' | tr -s "AB"
A-B-C

tr 还可以使用字符类来表示集合

  • [:alnum:]:字母和数字
  • [:alpha:]:字母
  • [:cntrl:]:控制(非打印)字符
  • [:digit:]:数字
  • [:graph:]:图形字符
  • [:lower:]:小写字母
  • [:print:]:可打印字符
  • [:punct:]:标点符号
  • [:space:]:空白字符
  • [:upper:]:大写字母
  • [:xdigit:]:十六进制字符

用法:

echo "AB" | tr "[:upper:]" "[:lower:]"

sort

sort 用来对文本中的每一行进行排序。

示例:

# 默认排序
sort file.txt

# 按数学顺序排序
sort -n file.txt

# 逆序排序
sort -r file.txt

# 判断文件是否经过排序
sort -c file.xtt

根据键或列来排序

常常看到这样的文本内容,希望以每行的数字来进行排序

james 23
kobe 24
wade 3

这类文件的内容,使用分隔符分为了多列,利用 sort 的 -k 选项可以指定以第几列作为排序的依据:

# 依据第二列,按数字顺序排序
cat list.txt | sort -nk 2
wade 3
james 23
kobe 24

有的时候文本并没有使用空格隔成列,比如:

b78434
s89988
c89878

这是一列列编号,假如希望以其中第 2 和第 3 个字符来进行排序,可以这样:

cat No.txt | sort -nk 2,3

这里的 2,3 指定了起始位置。

uniq

uniq 用来消除重复的行。它只能接受排序后的内容作为输入,基本用法如下:

sort file.txt | uniq

显示唯一的行

使用 -u 选项,只显示唯一的行,而不是多行被压缩至一行。

显示重复次数

使用 -c 选项,可以显示每一行重复出现的次数。

找出重复的行

使用 -d 选项,可以找到那些重复出现的行。

指定比较的区间

如果 sort 可以使用 -k 选项指定每行中要进行比较的内容一样,uniq 也可以指定,它是通过 -s-w 来完成的

  • -s:指定可以跳过前 n 个字符
  • -w:指定用于比较的最大字符数

mktemp

这个命令用来生成临时文件或临时文件夹,常常需要一个临时文件来保存一些内容,这个时候该命令就很有用了。

# 创建临时文件
file=`mktemp`

# 创建临时目录
dir=`mktemp -d`

# 仅仅生成文件名,而不实际创建文件或目录
tmpfile=`mkdir -u`

# 根据模板来创建,这里的 XXX 会被替换为随机字符,至少要保证存在 3 个及 3 个以上的 X
mktemp test.XXX

用 dd 生成任意大小的文件

dd 命令可以用来生成指定大小的文件,其使用方法如下:

dd if=/dev/zero of=junk.data bs=1M count=1

其中各个字段的意思是:

  • if:input file,默认 stdin
  • of:output file,默认 stdout
  • bs:块大小,可以是 1k 1G 等
  • count:需要多少块

文本文件的交集和差集

comm 命令用来比较两个排过序的文件

使用 chmod 设置文件权限

# 设置权限
$ chmod u=rwx g=rw o=r filename
$ chmod 764 filename

# 给某类用户增加或取消某个权限
$ chmod o+x filename
$ chmod u-x filename
  • o: other
  • u: user
  • g: group
  • a: all

设置粘滞位

粘滞位是一种给目录设置的权限类型,通过给目录设置粘滞位,目录内的文件只有目录所有者才能删除。

$ chmod a+t dir_name

递归地设置权限

使用 -R 选项指定以递归的方式修改权限:

$ chmod 777 . -R

链接

链接分为软连接和硬链接,软链接又被称为符号链接。

软链接

软链接包含一个绝对路径,指向另外一个文件,被指向的文件可以不存在。被指向的文件被删除后,软连接还存在,只是打不开什么东西了,这和 Windows 中的快捷方式的概念类似。

使用如下命令创建软链接:

ln -s app.js app.link.js

然后使用 ls -l 就能看到:

lrwxr-xr-x   1 xxx  yyy     5B 12 11 14:06 app.link.js -> app.js

这里指 app.link.jsapp.js 的符号链接。

硬链接

硬链接存储的是文件的 inode 号,inode 中记录着文件内容在磁盘上存放的位置。因此硬链接实际上是给文件创建了多个别名,每创建一个硬链接,文件的 inode 引用数量就会加 1,这个数量可以在 ls -l 得到的结果的第二项中看到,当一个文件删除了以后,这个数量就会减小,直到该数量减到 0 ,文件才会被删除。因此创建了硬链接后删除原文件,不会对链接文件造成影响。甚至可以说它们是相同的,不存在谁是谁的链接这种说法,他们都保存了 inode,通过 inode 来获取到文件信息。

# 打印前十行
head file

# 打印前 m 行
head -n m file

tail

# 打印最后十行
tail file

# 打印后 m 行
tail -n m file

只列出目录

# 利用目录后的斜杠
ls -F | grep "/$"

# 利用文件的类型
ls -l | grep "^d"

# 使用 find 的 type 选项
find . -type d -maxdepth 1 -print

使用 pushd 和 popd 来切换目录

在命令行中如果要经常切换目录,恰恰这两个目录又不是父子关系的时候,可能每次都要键入很长的目录。使用 pushd 和 popd 可以将目录压入栈中,便于快速切换。

# 压栈并切换目录
pushd /var/www

# 回到栈中的第 2 个(从 0 开始计数)目录
push +1

# 查看栈中的内容
dirs

# 回到堆栈中的上一个目录
popd

# 回到之前的目录
cd -

wc

# 统计行数
wc -l file

# 统计单词数
wc -w file

# 统计 byte 数
wc -c file

# 统计字符数,涉及中文字符的时候使用这个才准确
wc -m file

打印目录树

# 只打印目录
tree . -d

# 打印出文件大小
tree . -h

字符串操作

提取部分字符串

针对 名称.扩展名 这样的字符串,可以使用 %# 操作符来提取它的名称和后缀。

  • ${VAR%.*}:从 VAR 中删除 % 右边指定的通配符匹配的字符串,通配符由右向左匹配。
  • ${VAR#*.}:从 VAR 中删除 # 右边指定的通配符匹配的字符串,通配符由左向右匹配。

另外还有 %%## 他们是 %# 的贪婪版本。

cut

cut 是一个可以按列切分文件的工具。使用 cut 可以轻松提取出文本中的某几列,在制作报表的时候很有用。

下面以这个文件为例子:

list.txt:

file1.txt 1.1kb 2016-10
file2.cpp 2.4kb 2016-11
file3.css 3.1kb 2016-09

提取特定的列

# 提取第二列(文件大小),使用空格做定界符
cut -f 2 -d " " list.txt

列与列之间总需要有个符号来隔开,默认 cut 使用制表符来分隔,但是这里是空格,因此需要使用 -d 选项指明定界符为空格。使用 -f 选项指明需要提取第几列。

# 提取第一列和第二列
cut -f 1,2 -d " " list.txt

提取字符串的某个范围

可以从每行中提起特定范围的字符串

# 提取每一行的第一至第二个字符
cut -c 1-2 list.txt

# 提取前三个字符
cut -c -3 list.txt

# 提取从第 3 个字符开始至行位的字符
cut -c 3- list.txt

grep

搜索包含特定内容的文本行

grep pattern filename
# 如
grep hello main.cpp

使用正则表达式

grep -E "[a-z]+" filename
# 或者
egrep "[a-z]+" filename

只输出匹配的部分

grep -E -o "[a-z]+" filename

反转匹配结果

grep -v "hello" main.cpp

输入匹配的行数

grep -c "text" file.txt

这里只是匹配的行数,而不是次数,因为一行可能会出现多个匹配项。要想统计匹配次数可以使用:

grep -o "text" file.txt | wc -l

打印出行号

grep -n "text" wait.md

搜索包含某段文本的文件

grep -l "text" file1.txt file2.txt file3.txt

使用 -L 选项会反过来,列出不包含某段文本的文件,也就相当于 -lv

递归搜索

grep -R "text" ./src

忽略大小写

grep -i "TExt" file.txt

指定多个模式

grep -e "hello" -e "world" file.txt

从文本中读取匹配模式

可以将模式写入文件,然后使用 -f 命令来读入模式

# pattern.txt 中每一行都是一个模式
grep -f pattern.txt file.txt

指定包含或排除文件

使用 --include--exclude 可以指定包含或排除一些文件

# 只在 css 和 less 文件中寻找
grep -r "padding" . --include *.{css,less}

使用 0 值字节作为终结符

grep -rZl "hello" . | xargs -0

grep 常常用来输出一些文件,然后使用 xargs 来处理文件,这个时候如果文件名中包含空格,那么就会出现问题。因此需要使用 \0 来做输出文件的分隔,在 xargs 中使用 -0 指定使用 \0 作为分隔符。在 grep 中指定 -Z 就能使用 \0 来做分隔符。

静默模式

有时候只想知道是否能够匹配成功,这个时候可以使用静默模式,不会打印出任何东西,指向完成后可以通过 $? 来判断是否匹配成功。匹配成功为 0。

查看匹配行的前后几行的内容

# 查看匹配项以及后面 2 行
➜ seq 10 | grep 5 -A 2
5
6
7

# 查看匹配项以及之前的 2 行
➜ seq 10 | grep 5 -B 2
3
4
5

# 查看匹配项以及前后 2 行
➜ seq 10 | grep 5 -C 2
3
4
5
6
7

seq 10 生成 1~10 的序列,每行一个数。

sed

sed(stream editor)常用来进行文本替换。

使用 sed 替换文本中的字符串

sed 's/pattern/replace/' file

默认情况下 sed 会打印出替换后的文本内容。为了将其写回文件可以使用 -i 选项:

# 使用 i 选项将替换后的内容写回文件中
# 这里 -i 需要指定一个字符串,sed 会将其拼接在原文件名的后面得到新的文件名来进行保存原文件,作为备份,
# 然后将替换后的内容写入原文件。如果指定一个空字符串,表示新生成的文件和原文件一样,也就是覆盖原文件。
sed -i "" 's/H/h' file.txt

另外使用 's/pattern/replace/' 这样的方法只会替换第一次出现的字符串。如果要全部替换,需要使用 's/pattern/replace/g'

# 使用 g 表示全部替换
sed 's/this/This/g' file.txt

# 使用 Ng 表示从第 N 次出现开始替换
sed 's/this/This/4g' file.txt

移除空白行

# /pattern/d 用来移除匹配的行
sed '/^$/d' file

使用正则表达式来进行匹配

sed `s/[0-9]/x/g` list.txt

咋正则表达式中出现 {,} 等特殊字符要进行转译 \{, \}

使用已匹配字符串的标记( &

# & 代表已匹配的字符串
$ echo "1234" | sed 's/[1-9]/[&]/g'
[1][2][3][4]

使用正则表达式的分组

# 这里 \1 代表的是正则表达式中第一个分组匹配的内容
echo "--xxx--yyy" | sed 's/--\([a-z]\{3\}\)/**[\1]/g'

sed 表达式的组合

sed 'expression' | sed 'expression'
# 等价于
sed `expression;expression`
# 或者
sed -e 'expression' -e 'expression'

另外 sed 表达式可以使用双引号括起来,这个时候表达式会被求值。

$ echo 'var logName = env.LOGNAME' | sed "s/env.LOGNAME/$LOGNAME/"
var logName = wy

tar

tar 命令用来将多个文件或文件夹归档至单个文件。

使用 tar 对文件进行归档

tar -cf output.tar file1 file2 file3

-c 代表 create,-f 代表 specific file

-f 必须在最后一个,后面紧跟目标 tar 文件

使用 -t 和 -v 选项查看归档细节

tar -cvtf output.tar file1 file2 file3

使用 -r 向归档文件中追加文件

tar -rf output.tar file4

使用 -x 选项将归档文件中的内容提取出来

tar -xf archive.tar

可以使用 -C 选项来指定提取到的目录

tar -xf archive.tar -C /home/code

也可以只提取部分文件

tar -xf archive.tar file1 file2

这样就只提取 file1, file2 到当前目录

配合 stdin 和 stdout 使用

tar -cvf - file1 file2

使用 -A 选项拼接两个归档文件

tar -Atf archive1.tar archive2.tar

使用 -u 来更新 tar 包中的文件

tar -uf archive.tar file1

只有 file1 比 tar 包中的 file1 更新的时候才起作用

使用 -d 来比较 tar 包中的文件与文件系统的差异

tar -df archive.tar file1

从 tar 包中删除指定文件

tar -f archive.tar --delete file1 file2

压缩 tar 包

tar 包只是归档,但没有压缩,通常使用不同的压缩方式,会给 tar 包指定不同的后缀

  • gzip: file.tar.gz,使用 -z 选项
  • bunzip2: file.tar.bz2,使用 -j 选项
  • lzma: file.tar.lzma,使用 –lzma 选项

也可以不指定压缩方式,使用 -a 选项,tar 能根据指定的 tar 包的后缀来选择使用合适的压缩方式。

使用 --exclude 来排除部分文件

因为 tar 在指定文件的时候可以使用通配符,因此可以使用该选项来排除部分文件

gzip

gzip 用来压缩文件,且只能压缩单个文件,多个,需要先使用 tar 命令打包,然后再压缩

压缩文件

gzip file

解压缩

gunzip file.gz

列出压缩文件的信息

gzip -l test.txt.gz

使用 stdin 和 stdout

cat file | gzip -c > file.gz

使用 -c 可以指定将压缩结果输出到 stdout,然后利用管道定向到一个文件中

指定压缩级别

使用 --fast--best 来指定压缩率最低和最高的压缩级别

wget

wget 用于下载指定 url 对应的内容,基本用法如下:

$ wget URL1 URL2 URL3 ...

使用 -O 选项指定文件名

wget 默认会使用 url 中对应的文件名来存储文件,如果希望指定另外一个名字,可以使用 -O 选项。

$ wget http://example.com/file.png -O download.png

使用 -o 选项将输出写出文件

wget 在下载内容的时候,屏幕上会输出实时的进度,如果不希望它显示在屏幕上,可以使用 -o 选项将其写入文件。

$ wget http://example.com/file.png -O download.png -o download.log

使用 -t 指定重试次数

如果失败可能中断,那么指定一个重试次数将会很有用。

# 重试 5 次
$ wget -t 5 URL

# 不断重试
$ wget -t 0 URL

限制下载速度和下载文件大小

使用 --limit-rate 来限速:

# 限制最大速度为 20k/s
$ wget --limit-rate 20k URL

使用 --quota 或者 -Q 选项来限制最大下载大小,这可以避免不经意间下载太多东西占满了磁盘

# 最多下载 20M 的内容
$ wget -Q 20m URL