shell-cheet-sheet

bash 命令

sed(待补充完整)

sed 命令是用来处理文本行的

sed is a stream editor that works on piped input or files of text

以例子说明其用法

文件内容如下:a.py

def fizz_buzz(limit):
    for i in range(1, limit+1):
        if i % 3 == 0:
            print('fizz', end="")
        if i % 5 == 0:
            print('fizz', end="")
        if i % 3 and i % 5:
            print(i)
def main():
    fizz_buzz(10)
if __name__ == "__main__":
    main()

例子 1:使用正则表达式做文本替换

此处的 s/to/do/ 表示将 to 替换为 dos 表示 substitute(替换)。

$ echo howto | sed "s/to/do/"
howdo

例子 2:挑选指定行

此处表示挑选第 4 至第 5 行打印,在默认情况下 sed 命令会将每行都打印出来,使用 -n 选项可以抑制这一行为,4,5p 中的 p 表示 print(打印)。

$ sed -n "4,5p" a.py
            print('fizz', end="")
        if i % 5 == 0:

可以使用 -e 选项选择多个行,注意到第 2 行与第 3 行打印了两次

$ sed -n -e "1,4p" -e "2,3p" a.py
def fizz_buzz(limit):
    for i in range(1, limit+1):
    for i in range(1, limit+1):
        if i % 3 == 0:
        if i % 3 == 0:
            print('fizz', end="")

可以使用 5~3p 这种写法表示从第 5 行开始每隔 3 行进行打印

$ sed -n -e '5~3p' a.py
        if i % 5 == 0:
            print(i)
if __name__ == "__main__":

例子 3:文本替换

只替换第一个匹配

$ sed -n 's/=/!/p' a.py
        if i % 3 != 0:
            print('fizz', end!"")
        if i % 5 != 0:
            print('fizz', end!"")
if __name__ != "__main__":

替换所有匹配

$ sed -n 's/=/!/gp' a.py
        if i % 3 !! 0:
            print('fizz', end!"")
        if i % 5 !! 0:
            print('fizz', end!"")
if __name__ !! "__main__":

另外也可以进一步将 gp 换成 gip,其中 i 表示忽略大小写(case insensitive),另外 gip 三个字母的顺序可以随意调换

取前四行,将连续多个空格替换为一个空格

$ sed -n '1,4s/  */ /gp' a.py
def fizz_buzz(limit):
 for i in range(1, limit+1):
 if i % 3 == 0:
 print('fizz', end="")

多个替换规则,两种写法均可,注意到第 2 行没有任何模式被匹配上,由于 -n 参数的原因没有被打印出来。

$ sed -n -e '1,4s/zz/aa/gp' -e '1,4s/==/!=/gp' a.py
$ sed -n '1,4s/zz/aa/gp;1,4s/==/!=/gp' a.py
def fiaa_buaa(limit):
        if i % 3 != 0:
            print('fiaa', end="")

dirname

$ dirname <file|dir> # 返回文件或目录的父目录

tee(待补充)

$ tee a.txt b.txt  # 同时向 a.txt 和 b.txt 中写入相同的内容,输入这行命令后,需要继续输入要写入的内容,以 Ctrl+Z 结束。注意 tee 命令写入的东西同时也会打印至屏幕上。

一般使用管道的方式进行使用,例如

$ echo "abc" | tee a.txt b.txt
abc  # 写入文件并同时将写入的信息输出至标准输出流
$ echo "abc" | tee a.txt > b.txt  # 用重定向的方式同时写入两个文件,并且不显示在屏幕上

一个常见的错误参考例 7:修改屏幕亮度

printf

$ printf "%-5s %-10s %-4.2f\n" 123 acb 1.23

printf 命令仿照 C 语言的 printf 函数,用于格式化输出,格式控制符 %-5s 表示左对齐(不加 - 则表示右对齐),最少使用 5 个字符长度,以字符串的形式输出。格式控制符 %-4.2f 表示最少使用 4 个字符长度,小数点后保留 2 位,以浮点数形式输出。

!

在 shell 中,! 被称为 Event Designators,用于方便地引用历史命令。

  • !20 表示获取 history 命令中的第 20 条指令;

  • !-2 表示获取 history 命令中的倒数第 2 条指令;

  • !!!-1 的一个 alia;

  • !echo 表示最近地一条以 echo 开头的指令;

  • !?data 表示最近的一条包含 data 的指令

echo、stty

echo 命令可以使用 -e 参数使得输出字符串中的转义字符产生效果;另外,echo 命令还可以控制输出的字体颜色,详细情形不赘述。

$ echo -e "1\t2\t"
1	2	
$ echo -e "\e[1;31mred\e[0m" # 输出的字体颜色为红色
#!/bin/bash
# filename: password.sh
echo -e "Enter password"
stty -echo
read password
stty echo
echo Password read
echo "password is $password"

alias

别名相当于自定义命令,可以使用 alias 命令实现,也可以定义函数实现。此处仅介绍 alias 命令。

$ alias myrm='rm -rf'
$ myrm data/

alias 命令产生的别名只在当前 shell 有效

du 与 df(待补充)

linux 中,目录本身占用一个 block,其大小为 4K,用于存储一些元数据,例如权限信息、修改时间等。

du 命令意为 disk usage,用于显示目录或文件的大小

# 最基础的用法: -h意为--human-readable,表示自适应地使用K/M/G来显示文件(夹)大小

# 递归地显示<dir>以及所有子目录的大小(不会显示目录下的单个文件大小)
# 目录大小 = 目录大小(4kB)+当前目录下文件大小总和+子目录大小总和
$ du -h <dir>

# 显示<file>文件大小
$ du -h <file>
$ du -sh <dir>  # 只显示<dir>目录的大小
$ du -h --max-depth=1 <dir>  # 只显示<dir>目录以及一级子目录大小
$ # 注意-s与--max-depth不能混用, --max-depth=0与-s的作用一致
$ du -h --max-depth=1 --exclude=<subdir> <dir>  # 输出时不显示<subdir>的大小
$ du -Sh --max-depth=1 <dir>  # 计算目录大小时不包括子目录大小

df 命令意为 disk free

$ df -h  # 查看挂载信息

watch

# 每秒钟执行一次nvidia-smi命令以监控GPU使用情况,按ctrl+c退出监控
$ watch -n 1 nvidia-smi

sort

$ docker images | sort -n -k 7

sort 命令的作用是以行为单位进行排序。-n 选项表示把字符当作数字进行排序,-k 7 选项表示选择第 7 列进行排序。可以使用 -t : 来指定列的分割符为 :,可以使用 -r 选项进行降序排列(默认是升序排列)

$ cat a.txt | sort -t ":" -k 2nr -k 3

先按第二列降序按数值大小排列, 再按第三列排序。

更多玩法可参照博客

paste

paste -d= a.txt b.txt > c.txt

a.txtb.txt 按行进行拼接,拼接字符为 =,即:

paste -sd+ a.txt | bc

-s表示把 a.txt 的每行进行拼接,拼接字符为 +,此条命令用于对一列数字求和

history(待补充)

history 命令用于显示历史命令。经过测试发现它的行为与 ~/.bash_history 文件有关,一个可以解释的逻辑如下,系统保留有一份历史命令的记录文件,进入一个终端后,执行任意一条指令都会在 .bash_history 文件末尾追加一条记录(不同终端有着独立的记录,互不影响),退出终端时,

打开两个终端,分别输入(各条命令的实际):

$ echo dosome1
$ echo dosome3
$ echo dosome2
$ echo dosome4

mount/umount

$ mount -t nfs <device> <dir>  # 将设备/目录device挂载到目录dir(称为挂载点)上, -t参数表示指定档案系统型态, 通常无需指定
$ umount <device/dir>  # 取消挂载, 用挂载点或者设备名均可

若 umount 时出现 device is busy 的错误,可以参照链接进行解决,摘录如下:

$ # fuser命令使用 apt install psmisc 进行安装
$ fuser -m -v <dir>  # 显示所有占用该目录的进程
$ fuser -k <dir>  # 杀死所有占用该的进程
$ umount <dir>

$ # 以下为手动删除的办法, 但有时不奏效
$ # ps aux | grep <pid>  # 显示该进程的信息
$ # kill -9 <pid>  # 可以直接杀死进程

ps(待补充)

用于列出所有进程

参考链接

nmap

打印本机打开的所有端口信息

$ # apt install nmap
$ nmap 127.0.0.1

ln

$ # ln -s 原始路径 目标路径
$ ln -s /home/to/directory /data  # 得到/data/directory

file -i

$ file -i 文件名  # 查看编码格式

command

command 用于 shell 脚本中,忽略脚本定义的函数,而直接去寻找同名的命令

function ls() {
  echo "haha"
}
command ls  # 输出: 当前路径下的文件(夹)
# 使用PATH环境搜索ls
command -p ls  # 输出: 当前路径下的文件(夹)

另一种情况更加常见:在 ~/.bashrc 中为 ls 定义了 alias

alias ls='ls --color=auto'  # 自动按文件类型及权限显示目录内容

这种情况下

command -p ls  # 输出不带颜色
command -V ls  # 输出: ls is aliased to `ls --color=auto`
command -v ls  # 输出: alias ls='ls --color=auto'

getopt

stackoverflow

rsync

参考阮一峰博客

rsync -anv source/ destination  # 测试, 不实际执行
rsync -av source/ username@remote_host:destination
rsync -av --delete source/ destination  # 确保两个目录完全一致

注:

  • 此处 source 后面的斜杠如果省略, 则目标位置将会形成 destination/source 的目录结构, 这可能不是所期望的

  • rsync 命令默认会同步以 . 开头的隐藏目录

awk

示例1

注意: 如果原始数据中以作为分割符, 但某些列可能为空字符串, 则必须指定

aaaa  bbbbb
  ccccc
awk -F'\t' '{print $1"\t"$2}' data.csv

示例2: 引入外部变量

var="123"
awk -v v=${var} '{print var" "$0}' data.csv

示例3: if

awk '{if($2==23) {print $0}}' data.csv

free

$ free -h
              total        used        free      shared  buff/cache   available
Mem:          3.8Gi       3.0Gi       300Mi       1.0Mi       523Mi       577Mi
Swap:         1.0Gi       585Mi       438Mi

在 Mem 这一行: total = used + free + buff/cache, buff/cache 是指可以被操作系统回收的内存, 而 free 是完全没有被占用的内存, 而 used 里面包含 shared, shared 主要是内核空间或共享库占的内存, 而 available 是估算新创建一个进程时可以使用的内存(注意是估算的, 不是精确值), 一般大于 free, 但小于 free+buff/cache.

在 Swap 这一行: total = used + free

操作系统能使用的内存可以认为是 Mem(total) + Swap(total), 而 Mem 一般是对应真正的内存, 而 Swap 一般是对应磁盘, 一般情况下, 操作系统为了计算机整体性能, 可能会将 Mem 与 Swap 互相交换 (例如把程序 A 的某部分内存从 Mem 暂时换到 Swap 上, 后续又换回来). 而有些编程语言允许将内存声明为 page-locked memory (也称为 pinned memory), 这样会避免操作系统将这部分内存放到 Swap 上, 从而保障这些内存的访问速度. 但注意, 显式声明 pinned memory 可能影响计算机整体性能, 应按需使用 (此处的所谓 page-locked memory 也就是 pytorch dataloader 里的那个参数 pin_memory=True).

nc: 测试服务端口是否已经可用于接受连接

$ until nc -z localhost 5000; do sleep 0.1; done

上述 nc 命令用于测试 localhost:5000 是否可以接受连接, until ...; do ...; done 是一个循环.

一般用于本地启动一个服务端程序绑定在 localhost:5000, 然后使用上述命令测试服务是否成功启动.

Shell命令组合例子

例 2:管道、curl、grep、cut 结合使用

$ curl --head --silent baidu.com | grep -i content-length | cut --delimiter=' ' -f2
81

curl 命令用来请求 Web 服务器。其名字的含义即为客户端(client)的 URL 工具。具体用法可以参照阮一峰博客

它的功能非常强大,命令行参数多达几十种。如果熟练的话,完全可以取代 Postman 这一类的图形界面工具。——阮一峰博客《curl 的用法指南》

grep 的 -i 参数表示匹配时忽略大小写

cut 命令用于切分字符串,有若干种用法:取出第 mm 个到第 nn 个字符;按分隔符取出第 kk 个字符串。此处 cut 命令中用 --delimiter=' ' 指定分割符为空格,-f2 表示取出以该分割符分割产生的第二项

例 3:source、export

通常情况下,执行如下语句

./a.sh

实际发生的事情是:创建一个子进程,在子进程中运行 a.sh,然后回到当前 shell 中。注意子进程是用 fork 的方式产生的,因此子进程的环境变量与当前的 shell 是完全一致的。因此 a.sh 中设置的环境变量不会影响到当前的 shell。例子如下:

# a.sh的内容
export FFFF=ffff
echo $ABCD
echo $FFFF
$ export ABCD=abcd
$ export -p | grep ABCD
declare -x ABCD="abcd"
$ ./a.sh
abcd
ffff
$ echo $FFFF  # 没有输出

source 的作用是在当前 shell 中运行脚本内容, 使得脚本中设置的环境变量会影响到当前的 shell。例如:

$ source a.sh
# 也等价于
$ . a.sh

备注:对于 source a.sh 这种执行方式来说,脚本中的 $0 为终端本身,例如:/bin/bash,而 ./a.sh 这种执行方式,脚本中的 $0 将会是 ./a.sh

例 4:带颜色的终端输出

echo -e "\e[44;37;5mabcs\e[0m"
  • \e[<...>m 表示 <...> 中的部分为设置输出格式(字体颜色,背景颜色,是否加粗,是否产生闪烁效果等),\e 也可以用 \033 代替。

  • 44;37;544 表示设置背景色为蓝色,37 表示设置前景色(也就是字体颜色)为白色,5 表示字体产生闪烁效果 。

    备注:这些数字实际上被称为 SGR 参数(Select Graphic Rendition) ,这些数字的顺序是不重要的,完整的列表可以参见 ANSI escape code - Wikipedia,简短版的说明可以参见 简书

  • \e[0m 表示设定回终端的默认值

例 5:排除某些子目录的复制

$ ls ./src | grep -v 'logs\|images' | xargs -i cp -r ./src/{} ./dst

排除 logsimages 子目录从 src 复制文件至 dst

ROOT
  - src
    - logs/
    - images/
    - models/
    - main.py
  - dst
  • grep -v 表示排除,'logs\|images' 表示或的关系

  • xargs -i 表示将前一步的结果放在 ./src/{}{} 处。

例 6:grep、xargs

find . -name "*.py" | xargs grep -n "Model"

查找当前目录及子目录下所有 .py 文件含有关键词 Model 的行及行号。

例 7:修改屏幕亮度

$ echo 5000 | sudo tee /sys/class/backlight/intel_backlight/brightness
# 不能使用 sudo echo 5000 > /sys/class/backlight/intel_backlight/brightness

例 8:依据 csv 格式文件执行命令(read 命令)

cpfiles.txt 文件如下,希望按照 csv 进行文件拷贝

a.txt,a1.txt
b.txt,b1.txt
$ cat cpfiles.txt | while IFS="," read src dst; do cp $src $dst; done
$ cat cpfiles.txt | while IFS="," read -a row; do cp ${row[0]} ${row[1]}; done

例 9:生成序列(seq 命令)

$ for i in $(seq 100000); do echo ${i} >> x.txt; done

例 10:打印 Git Objects

$ find .git/objects/ -type f|awk -F"/" '{print $3$4}' | while read -r obj; do echo =========; echo ${obj} $(git cat-file -t ${obj}); echo $(git cat-file -p ${obj}); done;

输出

=========
08585692ce06452da6f82ae66b90d98b55536fca tree
100644 blob 78981922613b2afb6025042ff6bd878ac1994e85 a.txt
=========
4b825dc642cb6eb9a060e54bf8d69288fbee4904 tree

=========
78981922613b2afb6025042ff6bd878ac1994e85 blob
a
=========
db28edd91114108e88d430f317c46f87e9cb2896 commit
tree 08585692ce06452da6f82ae66b90d98b55536fca author xxx <xxx@qq.com> 1663866229 +0800 committer xxx <xxx@qq.com> 1663866229 +0800 add a.txt

例 11: 跳过前 k 行

# tail -n +<N+1> <filename>
tail -n +3 a.txt  # 跳过前2行

例 12: 列出当前目录下各个文件或文件夹大小

find . -maxdepth 1 -print0 | xargs -0 -i du -sh {}

Shell 脚本示例

例 1:定时计数

#!/bin/bash
echo -n Count:
tput sc  # 保存当前光标位置

count=0
while true; do
  if [ $count -lt 15 ];then
    let count++
    sleep 1
    tput rc  # 将光标返回到上一个存储位置
    tput ed  # 清空当前光标到结尾的所有字符
    echo -n $count;
  else exit 0;
  fi
done

例 2:输入密码

#!/bin/bash
echo -e "Enter password"
stty -echo  # 抑制输出
read password
stty echo  # 显示输出
echo Password read
echo "password is $password"

例 3:移动文件

for subdir in $(ls ./train);
do
	for filename in $(ls ./train/${subdir});
	do
	mv ./train/${subdir}/${filename} ./temp/${filename};
	done
done

例 4:修改文件后缀

for file in $1/*.dat;
do
	mv "$file" "${file%.*}.txt";
done

Last updated