命令行的艺术 笔记
基础
man bash
学习 Bash 基础知识man
阅读文档apropos
查找文档有些命令并不对应可执行文件,而是在 Bash 内置好的,此时可以使用
help
和help -d
命令获取帮助信息type 命令
来判断这个命令到底是可执行文件、shell 内置命令还是别名>
和<
来重定向输出和输入|
来重定向管道>
会覆盖输出文件而>>
是在文件末添加。了解标准输出 stdout 和标准错误 stderr通配符
*
代表任意数量的字符?
字符代表单个字符[...]
匹配方括号之中的任意一个字符[start-end]
表示一个连续的范围[^...]
和[!...]
表示匹配不在方括号里面的字符,也可以是连续范围[!start-end]
{...}
表示匹配大括号里面的所有模式,模式之间使用逗号分隔{start..end}
会匹配连续范围的字符[]
需要匹配的字符集合。{}
代表一组表达式的与关系{...}
与[...]
有一个很重要的区别。如果匹配的文件不存在,[...]
会失去模式的功能,变成一个单纯的字符串,而{...}
依然可以展开$ ls [ab].txt ls: [ab].txt: No such file or directory $ ls {a,b}.txt ls: a.txt: No such file or directory ls: b.txt: No such file or directory
通配符是先解释,再执行
通配符不匹配,会原样输出,这条规则对
{...}
不适用上面所有通配符只匹配单层路径,不能跨目录匹配,如果要匹配子目录里面的文件
ls */*.txt
引用意味着保护在引号中的字符串
""
部分引用,允许解释变量名前缀 ($)、后引符 (`) 和 转义符 (\)''
全局引用,不允许解释变量引用,不会展开通配符变量引用会保留空白字符
$ hello="A B C D" $ echo $hello A B C D $ echo "$hello" A B C D
''
保护被引起字符串中的特殊字符$ ls first a.txt b.txt $ grep '[Ff]irst' *.txt a.txt:This is the first line of file1.txt. b.txt:This is the First line of file1.txt. # [Ff]irst 解释匹配到文件 first,因此等效于 grep first *.txt $ grep [Ff]irst *.txt a.txt:This is the first line of file1.txt. # 删除之后未匹配到,原样输出 $ rm first $ grep [Ff]irst *.txt a.txt:This is the first line of file1.txt. b.txt:This is the First line of file1.txt.
Bash 中的任务管理工具:
&
后台执行- ctrl-z 挂起当前任务
- ctrl-c 中断前台任务
jobs
列出所有后台任务fg %
指定后台任务转到前台bg
当前任务转到后台kill %
杀掉指定后台任务
基本网络管理工具:
ifconfig
是net-tools
套件提供的网络管理命令ip
是iproute2
套件提供的网络管理命令,功能更强大,并旨在取代后$ sudo ip addr add 192.168.0.193/24 dev wlan0 $ ip addr show wlan0 $ ip route show $ ip neighbour # mac addr $ sudo ip link set ppp0 down $ sudo ip link set ppp0 up
dig
和nslookup
用来解析域名,dig
可以得到更多的域名信息
grep
/egrep
正则表达式egrep
=grep -E
-i
–ignore-case-o
–only-matchin-v
–invert-match-A
–after-context-B
–before-context-C
–context
学会基本的文件管理工具:
ls
和ls -l
(了解ls -l
中每一列代表的意义),less
,head
,tail
和tail -f
(甚至less +F
),ln
和ln -s
(了解硬链接与软链接的区别),chown
,chmod
,du
(硬盘使用情况概述:du -hs \*
)。 关于文件系统的管理,学习df
,mount
,fdisk
,mkfs
,lsblk
。知道 inode 是什么(与ls -i
和df -i
等命令相关)。
日常使用
Bash 命令行
- Tab 自动补全参数
- ctrl-r 搜索命令行历史记录
- 重复按下 ctrl-r 会向后查找匹配项
- Enter 键会执行当前匹配的命令
- 右方向键会将匹配项放入当前行中,不会直接执行,以便做出修改
Bash 命令行移动
- ctrl-w 删除最后一个单词
- ctrl-u 删除行内光标所在位置之前的内容
- alt-b 和 alt-f 以单词为单位移动光标
- ctrl-a 光标移至行首
- ctrl-e 光标移至行尾
- ctrl-k 删除光标至行尾的所有内容
- ctrl-l 清屏
man readline
可以查看 Bash 中的默认快捷键。内容有很多,例如 alt-. 循环地移向前一个参数,而 alt-* 可以展开通配符
按下 alt-# 在行首添加
#
把它当做注释再按下回车执行。之后借助命令行历史记录,你可以很方便恢复你刚才输入到一半的命令xargs
将标准输入转为命令行参数-L
指定多少行作为一个命令行参数-P
最大并行数-i
或-I
将命令行参数传给多个命令$ find . -name '*.py' | xargs grep some_function $ cat hosts | xargs -I{} ssh root@{} hostname $ find . -name "*.c" | xargs -i cp {} ~ $ echo -e "a\nb\nc" | xargs -L 1 echo a b c $ echo -e "a\nb\nc" | xargs -L 2 echo a b c
parallel
在一台或多台计算机上并行的执行计算任务pstree -p
以一种优雅的方式展示进程树pgrep
和pkill
根据名字查找进程或发送信号(-f
参数通常有用)kill
发送信号给进程kill -l
所有信号种类man 7 signal
查看详细列表kill -STOP [pid]
发送STOP
信号到进程以停止进程
使一个后台进程持续运行
disown
是bash的内置命令,将指定任务从jobs
列表之中移除# 移出最近一个正在执行的后台任务 $ disown # 移出所有正在执行的后台任务 $ disown -r # 移出所有后台任务 $ disown -a # 不移出后台任务,但是让它们不会收到SIGHUP信号 $ disown -h # 根据jobId,移出指定的后台任务 $ disown %2 $ disown -h %2
nohup
命令实际上将子进程与它所在的 session 分离,不会自动把进程变为后台任务,所以必须加上&
符号$ nohup node server.js &
检查端口
netstat -lntp
或ss -plat
检查哪些进程在监听端口(默认是检查 TCP 端口; 添加参数-u
则检查 UDP 端口)lsof -iTCP -sTCP:LISTEN -P -n
lsof
来查看开启的套接字和文件uptime
或w
来查看系统已经运行多长时间alias
来创建常用命令的快捷形式。例如:alias ll='ls -latr'
创建了一个新的命令别名ll
可以把别名、shell 选项和常用函数保存在
~/.bashrc
,具体看下这篇文章。这样做的话你就可以在所有 shell 会话中使用你的设定把环境变量的设定以及登陆时要执行的命令保存在
~/.bash_profile
。而对于从图形界面启动的 shell 和cron
启动的 shell,则需要单独配置文件要想在几台电脑中同步你的配置文件(例如
.bashrc
和.bash_profile
),可以借助 Git 建立 repository变量和文件名中包含空格处理时要格外小心
- Bash 变量要用引号括起来,比如
"$FOO"
部分引用 - 使用
-0
或-print0
选项以便用NULL
代替来分隔文件名
$ locate -0 pattern | xargs -0 ls -al $ find / -print0 -type d | xargs -0 ls -al
- for 循环中循环访问的文件名含有空字符(空格、tab 等字符),只需用
IFS=$'\n'
把内部字段分隔符设为换行符
- Bash 变量要用引号括起来,比如
Bash
set
使用set -x
输出执行的那行命令set -v
set -e
发生错误时退出而不是继续运行set -u
来检查是否使用了未赋值的变量set -o pipefail
,它可以监测管道中的错误- 当牵扯到很多脚本时,使用
trap
来检测 ERR 和 EXIT。一个好的习惯是在脚本文件开头这样写,这会使它能够检测一些错误,并在错误发生时中断程序并输出信息:set -euxo pipefail trap "echo 'error: Script failed: see failed command above'" ERR
Bash subshell
- subshell 调用方式
- 运行另一个 shell 脚本
- 嵌在
()
里的一列命令在一个子 shell 里运行 - 管道产生子shell
- 子 shell 中的目录更改不会影响到父 shell
- 子 shell 里的变量实际为局部变量
- 子 shell 可用于为一组命令设定临时的环境变量
- 子 shell(使用括号
(...)
)是一种组织参数的便捷方式。一个常见的例子是临时地移动工作路径# do something in current dir (cd /some/other/dir && other-command) # continue in original dir
- subshell 调用方式
Bash 变量扩展
$#
参数个数$0
-$9
位置参数${name:?error message}
用于检查变量是否存在input_file=${1:?usage: $0 input_file}
只需要一个参数${name:-default}
变量为空时使用默认值output_file=${2:-logfile}
再加一个(可选的)参数,默认值为logfile
- 数学运算符
((...))
,例如i=$(( (i + 1) % 5 ))
- 序列
{1..10}
,例如for i in {1..5}; do echo $i; done
- 模式匹配截断字符串:
${var%suffix}
和${var#prefix}
。例如,假设var=foo.pdf
,那么echo ${var%.pdf}.txt
将输出foo.txt
${PARAMETER/PATTERN/STRING}
替换字符串
括号扩展(
{
…}
)来减少输入相似文本,并自动化文本组合# mv foo.txt foo.pdf ../ $ mv foo.{txt.pdf} ../ # cp foo foo.bak $ cp foo{,.bak} # mkdir -p test-a/subtest-{1,2,3} test-b/subtest-{1,2,3} test-c/subtest-{1,2,3} $ mkdir -p test-{a,b,c}/subtest-{1,2,3}
<(some command)
可以将输出视为文件。例如,对比本地文件/etc/hosts
和一个远程文件:diff /etc/hosts <(ssh somehost cat /etc/hosts)
编写脚本时,把代码都放在大括号里可以防止下载不完全代码被执行
{ # 在这里写代码 }
Here Documents 是一种定义字符串的方法,可以保存文字里面的换行或是缩排等空白字元
cat <<End-of-message ------------------------------------- This is line 1 of the message. This is line 2 of the message. This is the last line of the message. ------------------------------------- End-of-message
同时重定向标准输出和标准错误
>
重定向标准输出<
重定向标准输入/dev/null
特殊空设备> /dev/null
丢弃输出< /dev/null
发送 EOF 给程序并立刻断开输入- 为了保证命令不会在标准输入里残留一个未关闭的文件句柄捆绑在你当前所在的终端上,在命令后添加
< /dev/null
- There are two formats for redirecting standard output and standard error:
&>word
推荐使用>&word
- 等效于
>word 2>&1
$ ls -l &>logfile </dev/null $ ls -l >logfile 2>&1 </dev/null
查看编码信息
man ascii
查看具有十六进制和十进制值的ASCII表man unicode
,man utf-8
,以及man latin1
通用编码信息
使用
screen
或tmux
来使用多份屏幕,当你在使用 ssh 时(保存 session 信息)将尤为有用。而byobu
可以为它们提供更多的信息和易用的管理工具。另一个轻量级的 session 持久化解决方案是dtach
。ssh 中,了解如何使用
-L
或-D
(偶尔需要用-R
)开启隧道是非常有用的,比如当你需要从一台远程服务器上访问 web 页面。对 ssh 设置做一些小优化可能是很有用的,例如这个
~/.ssh/config
文件包含了防止特定网络环境下连接断开、压缩数据、多通道等选项:TCPKeepAlive=yes ServerAliveInterval=15 ServerAliveCountMax=6 Compression=yes ControlMaster auto ControlPath /tmp/%r@%h:%p ControlPersist yes
一些其他的关于 ssh 的选项是与安全相关的,应当小心翼翼的使用。例如你应当只能在可信任的网络中启用
StrictHostKeyChecking=no
,ForwardAgent=yes
。考虑使用
mosh
作为 ssh 的替代品,它使用 UDP 协议。它可以避免连接被中断并且对带宽需求更小,但它需要在服务端做相应的配置。获取八进制形式的文件访问权限(修改系统设置时通常需要,但
ls
的功能不那么好用并且通常会搞砸),可以使用类似如下的代码:stat -c '%A %a %n' /etc/timezone
使用
fpp
(PathPicker)可以与基于另一个命令(例如git
)输出的文件交互。将 web 服务器上当前目录下所有的文件(以及子目录)暴露给你所处网络的所有用户,使用:
python -m SimpleHTTPServer 7777
(使用端口 7777 和 Python 2)或python -m http.server 7777
(使用端口 7777 和 Python 3)。以其他用户的身份执行命令,使用
sudo
。默认以 root 用户的身份执行;使用-u
来指定其他用户。使用-i
来以该用户登录(需要输入_你自己的_密码)。将 shell 切换为其他用户,使用
su username
或者sudo - username
。加入-
会使得切换后的环境与使用该用户登录后的环境相同。省略用户名则默认为 root。切换到哪个用户,就需要输入_哪个用户的_密码。了解命令行的 128K 限制。使用通配符匹配大量文件名时,常会遇到“Argument list too long”的错误信息。(这种情况下换用
find
或xargs
通常可以解决。)当你需要一个基本的计算器时,可以使用
python
解释器(当然你要用 python 的时候也是这样)。例如:>>> 2+3 5
文件及数据处理
通过文件名查找文件
- 当前目录
find . -iname '*something*'
- 所有路径
locate something
(但注意到updatedb
可能没有对最近新建的文件建立索引,所以你可能无法定位到这些未被索引的文件)
- 当前目录
检索源码
ag
rg
Markdown,HTML,以及所有文档格式之间的转换,
pandoc
使用
jq
处理 JSON将 HTML 转为文本:
lynx -dump -stdin
。当你要处理棘手的 XML 时候,
xmlstarlet
算是上古时代流传下来的神器。使用
shyaml
处理 YAML。要处理 Excel 或 CSV 文件的话,csvkit 提供了
in2csv
,csvcut
,csvjoin
,csvgrep
等方便易用的工具。当你要处理 Amazon S3 相关的工作的时候,
s3cmd
是一个很方便的工具而s4cmd
的效率更高。Amazon 官方提供的aws
以及saws
是其他 AWS 相关工作的基础,值得学习。了解如何使用
sort
和uniq
,包括 uniq 的-u
参数和-d
参数,具体内容在后文单行脚本节中。另外可以了解一下comm
。了解如何使用
cut
,paste
和join
来更改文件。很多人都会使用cut
,但遗忘了join
。了解如何运用
wc
去计算新行数(-l
),字符数(-m
),单词数(-w
)以及字节数(-c
)。了解如何使用
tee
将标准输入复制到文件甚至标准输出,例如ls -al | tee file.txt
。要进行一些复杂的计算,比如分组、逆序和一些其他的统计分析,可以考虑使用
datamash
。注意到语言设置(中文或英文等)对许多命令行工具有一些微妙的影响,比如排序的顺序和性能。大多数 Linux 的安装过程会将
LANG
或其他有关的变量设置为符合本地的设置。要意识到当你改变语言设置时,排序的结果可能会改变。明白国际化可能会使 sort 或其他命令运行效率下降许多倍。某些情况下(例如集合运算)你可以放心的使用export LC_ALL=C
来忽略掉国际化并按照字节来判断顺序。你可以单独指定某一条命令的环境,只需在调用时把环境变量设定放在命令的前面,例如
TZ=Pacific/Fiji date
可以获取斐济的时间。了解如何使用
awk
和sed
来进行简单的数据处理。 参阅 One-liners 获取示例。替换一个或多个文件中出现的字符串:
perl -pi.bak -e 's/old-string/new-string/g' my-files-*.txt
使用
repren
来批量重命名文件,或是在多个文件中搜索替换内容。(有些时候rename
命令也可以批量重命名,但要注意,它在不同 Linux 发行版中的功能并不完全一样。)# 将文件、目录和内容全部重命名 foo -> bar: repren --full --preserve-case --from foo --to bar . # 还原所有备份文件 whatever.bak -> whatever: repren --renames --from '(.*)\.bak' --to '\1' *.bak # 用 rename 实现上述功能(若可用): rename 's/\.bak$//' *.bak
根据 man 页面的描述,
rsync
是一个快速且非常灵活的文件复制工具。它闻名于设备之间的文件同步,但其实它在本地情况下也同样有用。在安全设置允许下,用rsync
代替scp
可以实现文件续传,而不用重新从头开始。它同时也是删除大量文件的最快方法之一:mkdir empty && rsync -r --delete empty/ some-dir && rmdir some-dir
若要在复制文件时获取当前进度,可使用
pv
,pycp
,progress
,rsync --progress
。若所执行的复制为block块拷贝,可以使用dd status=progress
。使用
shuf
可以以行为单位来打乱文件的内容或从一个文件中随机选取多行。了解
sort
的参数。显示数字时,使用-n
或者-h
来显示更易读的数(例如du -h
的输出)。明白排序时关键字的工作原理(-t
和-k
)。例如,注意到你需要-k1,1
来仅按第一个域来排序,而-k1
意味着按整行排序。稳定排序(sort -s
)在某些情况下很有用。例如,以第二个域为主关键字,第一个域为次关键字进行排序,你可以使用sort -k1,1 | sort -s -k2,2
。如果你想在 Bash 命令行中写 tab 制表符,按下 ctrl-v [Tab] 或键入
$'\t'
(后者可能更好,因为你可以复制粘贴它)。标准的源代码对比及合并工具是
diff
和patch
。使用diffstat
查看变更总览数据。注意到diff -r
对整个文件夹有效。使用diff -r tree1 tree2 | diffstat
查看变更的统计数据。vimdiff
用于比对并编辑文件。对于二进制文件,使用
hd
,hexdump
或者xxd
使其以十六进制显示,使用bvi
,hexedit
或者biew
来进行二进制编辑。同样对于二进制文件,
strings
(包括grep
等工具)可以帮助在二进制文件中查找特定比特。制作二进制差分文件(Delta 压缩),使用
xdelta3
。使用
iconv
更改文本编码。需要更高级的功能,可以使用uconv
,它支持一些高级的 Unicode 功能。例如,这条命令移除了所有重音符号:uconv -f utf-8 -t utf-8 -x '::Any-Lower; ::Any-NFD; [:Nonspacing Mark:] >; ::Any-NFC; ' < input.txt > output.txt
拆分文件可以使用
split
(按大小拆分)和csplit
(按模式拆分)。操作日期和时间表达式,可以用
dateutils
中的dateadd
、datediff
、strptime
等工具。使用
zless
、zmore
、zcat
和zgrep
对压缩过的文件进行操作。文件属性可以通过
chattr
进行设置,它比文件权限更加底层。例如,为了保护文件不被意外删除,可以使用不可修改标记:sudo chattr +i /critical/directory/or/file
使用
getfacl
和setfacl
以保存和恢复文件权限。例如:getfacl -R /some/path > permissions.txt setfacl --restore=permissions.txt
为了高效地创建空文件,请使用
truncate
(创建稀疏文件),fallocate
(用于 ext4,xfs,btrf 和 ocfs2 文件系统),xfs_mkfile
(适用于几乎所有的文件系统,包含在 xfsprogs 包中),mkfile
(用于类 Unix 操作系统,比如 Solaris 和 Mac OS)。
## 系统调试
单行脚本
冷门但有用
更多资源
expr
:计算表达式或正则匹配m4
:简单的宏处理器yes
:多次打印字符串cal
:漂亮的日历env
:执行一个命令(脚本文件中很有用)printenv
:打印环境变量(调试时或在写脚本文件时很有用)look
:查找以特定字符串开头的单词或行cut
,paste
和join
:数据修改fmt
:格式化文本段落pr
:将文本格式化成页/列形式fold
:包裹文本中的几行column
:将文本格式化成多个对齐、定宽的列或表格expand
和unexpand
:制表符与空格之间转换nl
:添加行号seq
:打印数字bc
:计算器factor
:分解因数gpg
:加密并签名文件toe
:terminfo 入口列表nc
:网络调试及数据传输socat
:套接字代理,与netcat
类似slurm
:网络流量可视化dd
:文件或设备间传输数据file
:确定文件类型tree
:以树的形式显示路径和文件,类似于递归的ls
stat
:文件信息time
:执行命令,并计算执行时间timeout
:在指定时长范围内执行命令,并在规定时间结束后停止进程lockfile
:使文件只能通过rm -f
移除logrotate
: 切换、压缩以及发送日志文件watch
:重复运行同一个命令,展示结果并/或高亮有更改的部分when-changed
:当检测到文件更改时执行指定命令。参阅inotifywait
和entr
。tac
:反向输出文件shuf
:文件中随机选取几行comm
:一行一行的比较排序过的文件strings
:从二进制文件中抽取文本tr
:转换字母iconv
或uconv
:文本编码转换split
和csplit
:分割文件sponge
:在写入前读取所有输入,在读取文件后再向同一文件写入时比较有用,例如grep -v something some-file | sponge some-file
units
:将一种计量单位转换为另一种等效的计量单位(参阅/usr/share/units/definitions.units
)apg
:随机生成密码xz
:高比例的文件压缩ldd
:动态库信息nm
:提取 obj 文件中的符号ab
或wrk
:web 服务器性能分析strace
:调试系统调用mtr
:更好的网络调试跟踪工具cssh
:可视化的并发 shellrsync
:通过 ssh 或本地文件系统同步文件和文件夹ngrep
:网络层的 grephost
和dig
:DNS 查找lsof
:列出当前系统打开文件的工具以及查看端口信息dstat
:系统状态查看glances
:高层次的多子系统总览iostat
:硬盘使用状态mpstat
: CPU 使用状态vmstat
: 内存使用状态htop
:top 的加强版last
:登入记录w
:查看处于登录状态的用户id
:用户/组 ID 信息sar
:系统历史数据ss
:套接字数据dmesg
:引导及系统错误信息sysctl
: 在内核运行时动态地查看和修改内核的运行参数hdparm
:SATA/ATA 磁盘更改及性能分析lsblk
:列出块设备信息:以树形展示你的磁盘以及磁盘分区信息lshw
,lscpu
,lspci
,lsusb
和dmidecode
:查看硬件信息,包括 CPU、BIOS、RAID、显卡、USB设备等lsmod
和modinfo
:列出内核模块,并显示其细节fortune
,ddate
和sl
:额,这主要取决于你是否认为蒸汽火车和莫名其妙的名人名言是否“有用”