总结并积累个人的命令行工具的经验,关注Bash
、Zsh
和各类工具简单的使用。
相关参考https://github.com/jlevy/the-art-of-command-line、Bash(1)
Table of Content
- Table of Content
- Bash 基础
- 命令行工具
- Other one-liners
- 从文件获取命令参数,按行传输给程序
xargs
并行处理参数- 简单制表
- 查找
/usr/lib
下占内存最大的20个文件 - 以树状打印进程,可视化程序间关系
- 使用
lsof(8)
列出正在使用tcp监听网络接口的相关程序 - 构造一个detached process
- 快速建立一个http server
- 快速建立一个TCP Server
- bash 快速连接并发送一份tcp数据
- 使用
mktemp
构建临时文件 - 使用
seq
命令生成比{start...end}
更复杂的序列 - TTY源于TeleTYpewriter
- 使用RE处理跨行文本
- kill某一进程组
- 命令行与剪贴板交互
- AWK重命名列表序号,并拷贝到剪贴板
- 查询utf-8编码的文件里的unicode字符
- Processing files and data & System debugging(TODO)
- 取消bash history的数目限制
- Anaconda 更改python版本
Bash 基础
TODO completion
重定向方式
redirection、duplication、here document、 here string、precess substitution 以及指定descriptor打开文件
Here Document 和 Here String
对于输入重定向,here document和here string允许替代文件作为输入来源,简单示例
# 使用CSV表格format输出
TemplateString='${Name},您好,今天是${Time}, 大会员还有${Limit}天到期'
while IFS=',' read Name Time Limit; do
eval "echo $TemplateString"
done <<hereDOC
小明,周三,100
abc,`date`,0
hereDOC
>>>
小明,您好,今天是周三, 大会员还有100天到期
abc,您好,今天是2022年 7月 2日 星期六 14时03分03秒 CST, 大会员还有0天到期
Process Substitution
process substitution 应用于接受文件作为输入的程序,其构建FIFO管道,将进程的输出与管道连接起来。
# 打印构建的管道
echo <(echo hello)
>>> /dev/fd/63
# 合并两个表格
paste <(cut -f 2 table1) table2 <(echo "gene table3")
为Shell本身打开文件
使用[fd]<>[name]
的语法,打开特定文件并设置为fd。
# 打开/tmp/text,并设置为fd为3
exec 3<>/tmp/text
# 查看bash打开的文件
lsof -p $$
>>>
OMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 966453 zyh cwd DIR 253,0 4096 3410306 /home/zyh/pFedMe-master
bash 966453 zyh rtd DIR 253,0 4096 2 /
bash 966453 zyh txt REG 253,0 1183448 8651993 /usr/bin/bash
bash 966453 zyh mem REG 253,0 51832 8661222 /usr/lib/x86_64-linux-
bash 966453 zyh 0u CHR 136,1 0t0 4 /dev/pts/1
bash 966453 zyh 1u CHR 136,1 0t0 4 /dev/pts/1
bash 966453 zyh 2u CHR 136,1 0t0 4 /dev/pts/1
bash 966453 zyh 3u REG 253,0 0 5385085 /tmp/text
...
注意这里的exec
builtin是必须的,因为bash默认的执行模式是fork-exec,也即命令行打开的文件是子进程的,与bash本身无关。只有仅使用exec
才能避免fork
。
另外,一般不需要为Shell主动打开文件,不过如果想保持一个PIPE存活,那么就必须使用shell打开pipe,否则进程打开的pipe会自动关闭,在进程退出时。
使用set
更改bash选项
更常用于bash脚本,包括
-x
, debuggging output-u
, detect unset variable usage-e
, abort on errors
bash脚本的特殊参数
- positional parameters: $1, $2,…。为传入参数
*
,$*
等价于$1c$2c$3...
,c为IFS变量的第一个字符@
,与$*
一致,但当"\$@"
的效果为"$1" "$2" ...
。这一点在处理包含空格的参数时比较重要#
,positional parameters的个数?
,前一个程序的执行状态$
,当前shell的pid,通过命令很难直接获取!
,上一个后台task的pid0
,第一个参数,为命令名或script名
算术/逻辑运算
((arithmetic exprsssion))
,返回0,1作为判断,也有$((arithmetic expansion))
版本, 等价于let
buitin[[ conditional expression ]]
, 注意要有空格,基本与test
,[]
等价,不过常在判断中用[]
bash展开顺序
man bash
EXPANSION 节 的相关笔记。可以看作readline(3)的后续。
bash 展开发生在第一次 word split之后,展开顺序的重要性在于,以下操作不能逆序发生,这对理解bash行为很有帮助
- brace expansion
- tilde expansion, 具有
~
前缀的特殊内容,例如~
- parameter expansionr, 变量展开,最常见的,
- arithmetic expansion,用于变量的计算
- command substitution,较为常用
- word splitting
- pathname expansion,又称glob
理解这个展开特性很有用,但不能使用在这些比较角落的规则,就像尽量不要使用晦涩的操作符优先级顺序一样,强行利用bash展开顺序来写脚本的结果只能更坏。
Zsh的增强补全
kill
命令需要PID才能发送信号。但在zsh下,增强的补全机制可以允许使用对comm
栏进行搜索。此外,ls
能够通过补全浏览所有可选项。等等,TODO。
理解unix下的inode
- inode是文件的元信息,使用
stat <file>
查看的即为该部分数数据 - unix对文件的存储分为数据区和inode区两部分,区域在硬盘初始化时变已经确定,使用
df -i
可以查看包括inode区的信息 open(2)
文件,从文件名到数据的索引流程- 打开目录文件,查找到文件名对应的inode号
- 根据inode号找到文件的inode数据,进一步找到文件在数据区的位置
- inode信息并不包括文件名,文件名在目录的索引过程被使用
理解readline(3)
对于bash的作用
readline(3)
是bash默认使用的读取行的API,readline(3)
本身是一个很强大的库,能够提供‘编辑’能力,具体地:
设想一下,terminal如何识别用户输入的\b
(删除键)并执行相应的行为,read(2)
肯定做不到,你会得到"helll\bo world"
的字符串,需要自己识别并做出处理。而所谓editing的功能,就是这些readline(3)
会帮你完成。但readline(3)
的能力不仅限于此,它应当是bash最早解释key sequence的API,提供很多基础快捷键。
因此,想增强对bash输入输出的理解
man 3 readline
很值得参考。
Embarrassing Parallel
并行的最简单形式 —— 通过直接运行多个实例。
-
bash 的并行原语 ——
&
、wait
,编译示例:for file in ls *.cpp; do g++ -c $file &; done wait && g++ *.o
-
利用pipe进行并行。pipe默认分别在subshell中运行程序,程序间通过管道通信,但工作方式是非同步式的,有时可以作为并行使用。
使用cheat sheets查找在线手册
cheat.sh: Unified access to the best community driven cheat sheets repositories of the world.
cheat.sh
很好用,而且示例丰富,例如:
curl cheat.sh/tar
>>>
cheat.sheets:tar
# tar
# GNU version of the tar archiving utility
# An approach to backing up the current user's HOME, using tar(1) and Gzip
# compression. Permissions (modes) will be preserved. The filename format will
# be: UID:GID_DATE.tgz
#
# Replace 'DEVICE' with whichever device is applicable to you, but note that it
# must be in the '/media/USER' (where USER is the username) directory, else
# this won't work, unless you edit the formatting section of `printf`.
tar -czvpf "$(printf '/media/%s/%s/%d:%d_%(%F)T.tgz' "$USER" 'DEVICE' ${UID:-`id -u`} ${GID:-`id -g`} -1)" "$HOME"
# Delete file 'xdm' from the archive given to the `-f` flag. This only works on
# non-compressed archives, unfortunately, but those can always be uncompressed
# first, then altered with the `--delete` flag, after which you can recompress.
tar --delete -f xdm_edited.tar.gz xdm
...
可以作为man(1)
的替代和补充
命令行工具
Tmux - Re-attachable session
Use screen or tmux to multiplex the screen, especially useful on remote ssh sessions and to detach and re-attach to a session. byobu can enhance screen or tmux by providing more information and easier management. A more minimal alternative for session persistence only is dtach.
在SSH中利用Tmux运行datachable的终端
在远程运行耗时较长的程序时最好使用这个,避免意外退出ssh导致的进程中止。也可以使用
# 在后台运行'&',并忽略control terminal结束时发送的HUP信号。即,一个dedached程序
# 当然还需要重定向输入
nohup longrunning 2>&1 >/tmp/long.out &
当然,麻烦了许多
模板处理工具
- here document可以嵌入简单的模版
envsubst(1)
命令可以直接对模板文本中的shell变量进行替换(需要使用$
标识)- python中用
string format
也可以支持模版
文件的通用查询工具
stat
- display file status (inode)
fuser
- list process IDs of all processes that have one or more files open
lsof
- list open files
realpath
- resolve pathname
file
– determine file type
ls
- list directory contents
使用fzf或percol可以交互式地过滤输出
(TODO,应用类别)
需要安装。不过有zsh、oh-my-zsh会更方便。感觉自己也比较少用了。
ubuntu下使用update-alternatives
更改不同版本的软件
常见场景:
- 更改python3对应的次版本号。
- 更改默认使用的gcc版本。
update-alternatives
包括三种主要命令:query、install、config/auto
-
update-alternatives --query <target>
查询一个<target>
的所有<alternatives>
。 -
--install
,注册一个<alternative>
,具体地# update-alternatives --install <path to linked target> <taegt name> <path to excutable> <priority vaule> sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 90 --slave /usr/bin/g++ g++ /usr/bin/g++-9
-
--config <target>
手动选择<target>
的的<altenative>
,--auto <target>
已经<alternative>
的priority value自动选择。
改变文件的encoding
vscode可以直接操作,但是只能一个一个弄。命令行方式可以参考,主要使用命令
file -i
确定当前encodingiconv -l
列出支持的编码iconv -f <encoding1> -t <encoding2> inputfile [-o file]
其他话题
-
Job Management
&
,ctrl-z
,ctrl-c
,jobs
,fg
,bg
,kill
,pkill/pgrep
, etc. -
filesytem management
df
,mount
,fdisk
,mkfs
,lsblk
, etc. -
Network management
ip
orifconfig
,dig
,traceroute
,route
, etc. -
version control system
init, clone, fetch, merge, pull, push etc.
-
regular expressions
grep/egrep
,-i
,-o
,-v
,-A
,-B
,-C
-
package management softwares
apt-get
,yum
,brew
pip
(python based tools), etc.
Other one-liners
从文件获取命令参数,按行传输给程序
xargs
默认使用空格和换行分割参数,并为程序传入尽可能多的参数,因此一次传入的参数量一般不为一行,需要特殊指定为每行,参考# 通用, 注意—I具有限制,-L每行末尾不能是空行 cat /tmp/params_perline | xargs -I{} -L 1 my_program {} # GNU xargs, -d选项 cat /tmp/params_perline | xargs -d '\n' my_program
- read builtin,按行读取
需要注意行中的空格的影响,更直接的,可以使用while read line; do my_program $line; done < /tmp/params_perline
while read line; do eval "my_program $line"; done < /tmp/prarms_perline
xargs
并行处理参数
xargs中的命令不能传入&,但具有并行执行的参数
cat params | xargs -I{} -P 0 ./myprogram {}
简单制表
xargs
能够使用-n
限制传入参数,再将分隔符换为\t
即可完成简单制表。
head -n 100 /usr/share/dict/web2 | xargs -n 5 | tr ' ' '\t'
但这样会出现类似
DB Size_in_MB
foobar 11011.2
barfoo 4582.9
donkey 4220.8
shoryuken 555.9
hadouken 220.0
kong 214.8
super_mario_bros_p 211.1
的词长不匹配。使用column -t
能将这样长度不匹配的表格进行对其,
head -n 100 /usr/share/dict/web2 | xargs -n 5 | column -t
查找/usr/lib
下占内存最大的20个文件
因为du
不区分文件和目录,需要使用find
区分;使用sort -h
排序具有单位的数据。
find /usr/lib -type f | xargs du -h | sort -rhk 1
以树状打印进程,可视化程序间关系
pstree(1)
– list processes as a tree
# 打印 NetworkManager 及其子进程
pstree -p `pgrep NetworkManager`
使用lsof(8)
列出正在使用tcp监听网络接口的相关程序
sudo lsof -iTCP -sTCP:LISTEN -P -n
>>>
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
systemd-r 2194 systemd-resolve 13u IPv4 102699 0t0 TCP 127.0.0.53:53 (LISTEN)
cupsd 618782 root 6u IPv6 51371751 0t0 TCP [::1]:631 (LISTEN)
cupsd 618782 root 7u IPv4 51371752 0t0 TCP 127.0.0.1:631 (LISTEN)
...
构造一个detached process
在远程运行耗时较长的程序时需要避免意外退出ssh导致的进程中止,需要做的工作有:
- 程序置于后台(不占用terminal)
- 将程序从control terminal脱离(nohup)
- 将输入输出与control terminal去关联(否则可能会在输出时失败)
具体的解释仍有待完善(TODO:进程间的组织形式,group、session;特殊进程deamon的构造方法),但简单使用仅需要nohup
即可。
# nohup 将到terminal的标准输出和标准错误重定向到./nohup.out,忽略SIGHUP,使用&置于后台。
nohup <utility> [arguments] &
快速建立一个http server
# 在当前目录建立http server,并在9000端口侦听
python -m http.server 9000
快速建立一个TCP Server
# 监听9000端口
nc -kl 9000
bash 快速连接并发送一份tcp数据
# 与127.0.0.1:9000上的tcp server握手,并发送数据 "hello world\n"
echo hello world > /dev/tcp/127.0.0.1/9000
使用mktemp
构建临时文件
文件一般在/tmp
或/var/tmp
,会自动每隔10/30天自动清理
使用seq
命令生成比{start...end}
更复杂的序列
TTY源于TeleTYpewriter
使用RE处理跨行文本
sed和awk默认只能单行处理,perl可以使用-0777来将整个文档视为单行处理,而-p可以模拟sed
TODO:AWK应该也行
# 删除文本的空行
perl -0777 -pe 's/\n+/\n/g' file1
kill某一进程组
(TODO待完善)
对于多个关联的进程,例如jobs或由某一程序发起的一系列进程,例如
# 在命令行关闭vscode
kill `ps -o pgid,comm -A | grep "Visual Studio Code" | cut -d ' ' -f 1 | sort -rn | uniq`
关键是需要确定进程所属的进程组
,ps
的pgid
项便是所属进程组,而sort
、uniq
清除冗余,再使用kill
即可。
命令行与剪贴板交互
pbcopy
, pbpaste
- provide copying and pasting to the pasteboard (the Clipboard) from command line
# 拷贝“hello world\n”到剪贴版
echo hello world | pbcopy
AWK重命名列表序号,并拷贝到剪贴板
# 序号类型为 /[0-9][0-9]?\./
awk '
BEGIN {COUNT=1}
{ COUNT+=sub("[0-9][0-9]?\\.", sprintf("%d.", COUNT));
print}
' tables.txt | pbcopy
查询utf-8编码的文件里的unicode字符
对应uniutils
中的uniname
# 仅接受文件作为输入,简单使用为
uniname <(echo -e "\xF0\x9D\x9C\x82") # utf-8编码下的eta
---
character byte UTF-32 encoded as glyph name
0 0 01D702 F0 9D 9C 82 𝜂 MATHEMATICAL ITALIC SMALL ETA
1 4 00000A 0A LINE FEED (LF)
目前根据unicode name查找codepoint的工具,在vscode插件中能找到,命令行中还不太行。python的unicodedata包只有完全匹配的方法。
Processing files and data & System debugging(TODO)
https://github.com/jlevy/the-art-of-command-line
取消bash history的数目限制
在bash(1)
的Shell Variables
的两个参数:
- HISTFILESIZE - The maximum number of lines contained in the history file.
- HISTSIZE - The number of commands to remember in the command history.
其中负数为不设限。因此将
export HISTSIZE=-1
export HISTFILESIZE=-1
加入.bashrc
即可。如果仍有问题(不知道怎么就发生了 truncation),参考how-to-unlimited-bash-shell-history。
Anaconda 更改python版本
# 3.10
conda install python=3.10