Bash 学习笔记

Shell

Frequently Used Commands

Basic

open <dir/file>   # 在访达中打开 dir/file
start <dir/file>  # DOS 中的 open
xdg-open <dir/file>  # ubuntu 中的 open

clear  # 清屏
cls    # DOS 清屏

ls -a  # 查看当前目录所有文件(包括隐藏文件)

mv <file> <dir>     # 将 file 移动到 dir
mv <name1> <name2>  # 重命名

mkdir -p <dir>  # 创建目录(自动创建中间目录)

rm <file>     # 删除文件
rm -rf <dir>  # 强制递归删除
rmdir         # 删除空目录
del           # DOS 删除命令

cat <file>     # 查看文件内容
cat -n <file>  # 显示行号
type <file>    # DOS 中的 cat
tee <file>     # 将 stdin 的内容输出到 file 中,同时将 stdin 的内容输出到 stdout

less  # 分页显示文本文件内容
more  # 分页显示文本文件内容,比 less 功能少

touch <file>  # 创建文件

echo -e  # 使用转义字符
echo -n  # 关闭自动换行

ps  # process status,显示进程状态

cd -  # 回到上个目录

cmp file1 file2  # 显示两个文件的不同

sudo reboot  # 重启
sudo shutdown -h now  # 关机

sudo systemctl start sshd     # Linux 启动 sshd 服务;比 service 命令好
sudo systemctl daemon-reload  # 重新加载配置文件

abc/ 表示目录,abc 表示文件或目录——当目录存在时就是目录,目录不存在就是文件。

Advanced

ln -s <source_file> <target_file>  # 创建软链接

head -n 5 <file>  # 显示 file 前 5 行内容
tail -n 5 <file>  # 显示 file 后 5 行内容

tail -f <file>    # 根据文件描述符实时监控文件内容
tail -F <file>    # 根据文件名实时监控文件内容

read VAR  # 从终端读取用户输入,并赋值给 VAR 变量。
          # 按下 Ctrl + D 表示读取到文件流的末尾

basename "/bin/zsh"  # 获取文件名 (zsh)
dirname "/bin/zsh"   # 获取目录名 (/bin)

file --mime-encoding <file>  # 获取 file 的编码方式 (GBK 会被当作 iso-8859-1)

iconv -f gbk -t utf-8 <file>  # 将 file 以 GBK 编码打开,并以 UTF-8 编码输出到 stdin

find <file>  # 在当前目录下查找文件

which -a <prog>  # 找到 <prog> 的所有文件
whereis <prog>   # 找到 <prog> 的所有文件及 man 文件

tr -d '\r' <file>  # 删除文件中的 \r
echo $PATH | tr ':' '\n'  # 将 PATH 中的 : 替换为换行符

python3 --help | grep -C 2 pip  # -C 2 表示显示匹配行的上下 2 行

pbpaste  # 从剪贴板中读取内容
pbcopy   # 将剪贴板内容粘贴进来
# npm start sf 5-2 $(pbpaste)

# Stream Editor
# -i: in-place
sed -i.bak "s/hello/world/g" <file>  # 将 file 中的 "hello" 替换为 "world",并将原文件备份为 file.bak

tree  # 显示目录结构

arch  # 显示系统架构

nohup <prog> &  # & 用于将命令作为后台进程运行,nohup 会忽略 SIGHUP 信号,避免进程因终端被关闭发出的 SIGHUP 信号导致进程终止

Windows 上的 pbpaste:

pasteboard | GitHub

scoop bucket add extras
scoop install pasteboard

终端快捷键

  • ⌃R:搜索历史命令
  • ⌃D: 在新行输入,表示 EOF 。在行中输入,表示输出“标准输入”的缓冲区。此时连按两下 ⌃D 表示 EOF
  • ⌃Z: 挂起,可用 fg 命令恢复
  • ⌃C: 终止进程
  • ⌃V: 字面值输入
  • ⌃C⌃D 的区别:⌃C 是终止进程,⌃D 是结束输入

执行 Shell 脚本

#! 开头的语句叫做 Shebang,用于指定脚本解释器。

不推荐:

#!/bin/bash

推荐:

#!/bin/env bash

在新进程中运行 Shell 脚本

# 将 Shell 脚本作为程序运行
# 如果不写 ./,Linux 只会到系统路径(由 PATH 环境变量指定)下查找外部命令
chmod +x script.sh # 给脚本添加执行权限
./script.sh        # 运行脚本

# 将 Shell 脚本作为参数传递给 Bash 解释器
bash script.sh  # 这种方式将忽略脚本文件第一行的指定解释器信息

在当前进程中运行 Shell 脚本

source script.sh  # 在 zsh 下依然需要使用 ./script.sh
. script.sh       # source 命令的简化写法

Bash 配置文件

/etc/profile.d 下的所有 .sh 文件以及 /etc/profile 文件等价于全局的用户 ~/.profile 文件,会在每次登录时执行,而在 ~/.bash_profile 下的脚本只会在第一次登录时执行。

Shell 有多个配置文件。对普通用户来说,~/.bashrc 是最重要的文件,因为不管是否登陆都会加载该文件。

Git Bash for Windows 的配置文件位置在 C:\Program Files\Git\etc\profile.d\git-prompt.sh

flowchart TD X(登录) --> A0[登录 Shell] A0 --> A1["/etc/profile"] --> A2["/etc/profile.d"] A2 --> A3["~/.bash_profile"] A3 --> A4["~/.bash_login"] A4 --> A5["~/.profile"] A5 --> Y["~/.bashrc"] X --> B0["交互式非登录 Shell"] B0 -------> Y classDef c1 fill:#afa,stroke:#080; class A0,B0 c1; classDef c2 fill:#f96,stroke:#f66; class X,Y c2;

Shell 配置文件(配置脚本)的加载 | C 语言中文网

实例

# 给 PATH 变量增加新的路径
PATH="$PATH:$HOME/addon" # 将主目录下的 addon 目录也设置为系统路径
# 修改命令提示符的格式
PS1="[bash]\$" # 命令提示符改为 [bash]$

PATH 使用冒号 : 分隔不同的路径

PS1 和 PS2 中的特殊字符 | C 语言中文网

变量

在 Bash shell 中,每一个变量的值都是字符串。在默认情况下,Bash shell 不会区分变量类型。当然,如果有必要,也可以使用 declare 关键字显式定义变量的类型。

# 定义变量
variable=value    # 如果要赋的值不包含任何空白符及转义字符,那么可以不使用引号。不建议这种写法
variable='value'  # '' 相当于原生字符串,将不解析内容中的变量、命令
variable="value"  # 最常用的情况。会解析其中的变量,命令。但是不会解析转义字符

readonly variable # 将变量定义为只读变量
unset variable    # 删除变量。unset 命令不能删除只读变量

# 使用变量
author="John"
echo $author
echo "作者是 ${author}。" # 用花括号指明变量名的边界

⚠️ 注意: 不要使用 path 作为变量名,在大小写不敏感的系统中 path 等价于环境变量 PATH

Globbing

Bash 会在没有被引号包围的命令行参数上应用文件名扩展。

Wildcard (通配符)

*:表示任意长度的字符串,除了以 . 开始的文件名和包含路径分隔符 / 的文件名

?:表示一个字符

假设当前目录下有文件 file1.txt, file2.txt, ...,file9.txt,以及 filea.txtfileb.txtfilec.txt,那么:

file[135].txt  # 单字符匹配,匹配 file1.txt, file3.txt, file5.txt
file[1-9].txt  # 范围匹配,匹配 file1.txt, file2.txt, ..., file9.txt
file[!1].txt # 否定匹配,匹配 file2.txt, file3.txt, ..., file9.txt
file[1-3a-c].txt # 复合集合,匹配 file1.txt, file2.txt, file3.txt, filea.txt, fileb.txt, filec.txt

file[[:digit:]].txt  # 字符类,匹配 file1.txt, file2.txt, ..., file9.txt

常见的字符类包括 [:alpha:][:alnum:][:lower:][:upper:] 等。

说明

当使用方括号匹配时,如果模式不匹配任何文件或目录,它的行为将取决于 shell 的设置和选项(例如,Zsh 中的 nullglobnomatch 选项影响这种行为)。

参数展开

${...} 是参数展开的一般形式,在这种形式中,你可以对变量进行各种操作。

参数展开修饰符

示例

${(o)array}  # 对数组排序
修饰符 含义
L 将字符串转换为小写
U 将字符串转换为大写
f 将字符串按换行符分割为数组
o 数组按字典升序排序
O 数组按字典降序排序
n 数组按数值升序排序
k 展开关联数组的键
v 展开关联数组的值
@ 当数组被引号包围时,@ 确保每个元素被当作独立的个体处理

参数替换

var=${var:-new}  # 如果 var 为空或未定义,那么将 var 替换为 new

var=${var/old/new}  # 将 var 中的第一个 old 替换为 new
var=${var//old/new} # 将 var 中的所有 old 替换为 new
var=${var/#old/new} # 如果 var 的前缀匹配 old,那么将匹配的 old 替换为 new
var=${var/%old/new} # 如果 var 的后缀匹配 old,那么将匹配的 old 替换为 new

参数替换 | 简书

命令替换

将命令的结果赋给变量

var=`cat log.txt`  # 如果有多个命令,命令之间以分号 ; 分隔
var=$(cat log.txt) # 推荐,并且这种方式最常见
Fir_File_Lines=$(wc -l $(ls | sed -n '1p')) # 两层嵌套
# 注意,如果被替换的命令的输出内容有换行符,或者含有多个连续的空白符,那么在输出变量时应该将变量用双引号包围,否则系统会使用默认的空白符来填充。
echo "$var"

修改变量的值时不能在变量名前加 $,只有在使用变量时才能加 $

= 的周围不能有空格

变量的作用域

局部变量

function func() {
  local a=99  # 若不使用 local,则 a 是全局变量
}

全局变量

在当前 Shell 进程中有效。在 Shell 中定义的变量默认就是全局变量。

环境变量

How To Read and Set Environmental and Shell Variables on Linux | DigitalOcean

在所有的子进程中有效。

export a     # 将变量导出为环境变量
export b=22  # 在定义的同时导出为环境变量

环境变量被创建时所处的 Shell 进程称为父进程,父进程中创建的进程叫子进程。创建子进程最简单的方式是运行 bash 命令。通过 exit 命令可以一层一层地退出 Shell。

exit 可以返回命令的退出状态:exit 0

进入 Shell 子进程

特殊变量

变量 含义
$0 当前脚本的文件名
$1 传递给脚本或函数的参数(从 1 开始计数)
$# 传递给脚本或函数的参数个数
$* 传递给脚本或函数的所有参数
$@ 传递给脚本或函数的所有参数
$? 上个命令的退出状态,或函数的返回值
$$ 当前 Shell 进程 ID

$1 是一种宏替换

命令的退出状态由 exit 命令返回,函数的返回值由 return 返回

$*$@ 的区别

$*$@ 不被双引号 "" 包含时,它们没有任何区别。

当它们被双引号 "" 包含时,会有如下区别:

  • "$*" 会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。
  • "$@" 仍然将每个参数都看作一份数据,彼此之间是独立的。

$@ 更常用

位置参数

Shell 脚本文件和函数可以自动接收传入的参数,并以 $1, $2, ... 的方式表示。

如果参数个数达到了 10 个,那么需要用 ${10} 的方式指明参数名的边界

给脚本文件传递位置参数

script.sh:

#!/bin/bash

echo "Language: $1"
echo "Name: $2"
$ source script.sh Shell LiXiao
Language: Shell
Name: LiXiao
给函数传递位置参数
$ function func() {
$   echo "Language: $1"
$   echo "Name: $2"
$ }
$ func C++ LiXiao
Language: C++
Name: LiXiao

字符串

# 获取字符串长度
echo ${#string}  # 6

# 字符串拼接
str1="a"
str2="b"
str3=$str1$str2"c"  # 只需将字符串放在一起就能拼接

字符串截取

# 截取范围,从左边开始计数
$ str=111a222b333a444  # a 是第 3 位(从 0 开始计数)
$ echo ${str:3:5}  # 从第 3 位开始输出 5 位

a222b

$ echo ${str:3}  # 从第 3 位开始输出到末尾
a222b333a444

# 从右边开始计数
$ echo ${str:0-4:5}  # 从倒数第 4 位开始输出 4 位。b 是倒数第 4 位(从 1 开始计数)
b333a

# 截去子串
# 截去左边(截去前缀)
$ echo ${str#*a}  # 从左边截去第一个匹配 a 及之前的所有字符
222b333a444

$ echo ${str#111a}  # 省略通配符 *,此时需要写出完整的前缀
222b333a444

$ echo ${str##*a}  # 从右边截去第一个匹配 a 及之前的所有字符
444

# 截去右边(截去后缀)
$ echo ${str%a*}  # 从右边截去第一个匹配 a 及之后的所有字符
111a222b333

$ echo ${str%%a*}  # 从左边截去第一个匹配 a 及之后的所有字符
111

数组

数组的声明与使用

在 Zsh 中,数组索引从 1 开始计数;在 Bash 中,数组索引从 0 开始计数。

在 Shell 中,用 () 来表示数组,数组元素之间用空格分隔:

# 数组没有长度限制
nums=(1 2 3)  # 定义一个数组
nums=(1 2 3 "abc")  # 数组没有类型限制
nums+=(4 5 6)  # 在数组末尾添加元素
nums[20]=88  # 可以只给特定元素赋值
nums=([3]=24 [5]=19 [10]=12)

# 获取数组元素
echo ${nums[0]}  # 输出第 0 个元素:1(Bash)
echo ${nums[*]}  # 输出数组所有元素
echo ${nums[@]}  # 另一种写法

unset nums[0]  # 删除数组元素
unset nums  # 删除数组

⚠️ Zsh 中数组的行为与 Bash 有所不同:

array=(1 2 3)

echo $array  # Bash:输出第 0 个元素;Zsh:输出所有元素
echo ${array[1]}  # Bash:1;Zsh:2
echo $array[1]  # 只有 Zsh 可以省略 {}

获取数组长度

和获取字符串长度的方法类似,使用 # 号:

# 两种形式是等价的
echo ${#nums[*]}
echo ${#nums[@]}

数组拼接

先将数组展开,然后再合并到一起:

# 两种形式是等价的
arr=(${nums_1[*]} ${nums_2[*]})
arr=(${nums_1[@]} ${nums_2[@]})

关联数组

也即 Python 中的字典

定义与使用

declare -A color  # 定义一个名为 color 的关联数组
color["red"]="#ff0000"
color["green"]="#00ff00"
color["blue"]="#0000ff"

# 在定义的同时赋值
declare -A color=(
  ["red"]="#ff0000"
  ["green"]="#00ff00"
  ["blue"]="#0000ff"
)

echo $(color["red"])  # 访问关联数组

操作关联数组

Bash
# 获取所有键
# 两种形式是等价的
${!color[*]}
${!color[@]}
Zsh
# 获取所有键
${(k)color}
"${(@k)color}"  # @ 使得每个键作为独立个体存在,这种用法最常用

# 遍历所有键和值
for key val in ${(kv)color}; do
    echo "$key: $val"
done

数学运算

除了 C 语言中包含的所有算数运算符外,Shell 还包括幂运算符:**

整数运算

将表达式写在双小括号之间:((1+2))

多个表达式用 , 分隔,最后一个表达式的值将作为命令的执行结果。

使用 $ 获取 (( )) 命令的结果。

Shell 中还有 let 命令,$[] 命令,它们的功能和 (()) 一样,都是计算整数。 除此之外,还有 expr 命令也可以用于整数计算。

e.g.

$ ((a=2-1))
$ ((b=a+4))
$ echo $((1+2, 2**3))
8
$ echo $((a<2&&b==5))
1
$ echo $((a++))
1

浮点数运算

使用 bc 命令

一行一个表达式,或者一行中多个表达式用分号 ; 分开。

quit 退出

C 语言中文网:Linux bc 命令

内置变量

变量名 作用
scale 指定精度,默认为 0
ibase 指定输入进制
obase 指定输出进制
last. 表示最近打印的数字

💡 Tip: obase 要尽量放在 ibase 前面,因为指定 ibase 后,所有后续输入的数字都是以 ibase 的进制来换算的。

内置函数

在输入 bc 命令时需要使用 -l 选项启用数学库。

函数名 作用
s(x) 正弦函数,x 是弧度值
c(x) 余弦函数,x 是弧度值
a(x) 反正切函数,返回弧度值
l(x) 自然对数函数
e(x) 自然指数函数
j(n,x) 贝塞尔函数,计算从 n 到 x 的阶数

在 Shell 中使用 bc 计算器

echo "expression" | bc  # expression 可以包含 Shell 中的变量:$var
var=$(echo "expression" | bc)

e.g. 进制转换:

# 10 进制转 16 进制
i=15
j=$(echo "obase=16;$i" | bc)
echo $j

# 16 进制转 10 进制
k=$(echo "obase=10;ibase=16;$j" | bc)  # 别忘了 obase 要写在 ibase 前面
echo $k
借助重定向使用 bc 计算器
var=$(bc << EOF  # 分界符(EOF)可以自定义
2*3
last/2
EOF

这种方式在有大量数学计算时比较方便

declare 命令

语法:

declare [-/+] [选项] expression

declare 和 typeset 都是 Shell 内建命令,它们用法相同,不过 typeset 已经弃用。

- 表示设置属性,+ 表示取消属性。

选项 含义
-f 列出之前由用户在脚本中定义的函数名称和函数体
-F 仅列出自定义函数名称
-g 在 Shell 函数内部创建全局变量
-p 显示指定变量的属性和值
-a 声明变量为普通数组
-A 声明变量为关联数组
-i 将变量定义为整型
-r 将变量定义为只读
-x 将变量设置为环境变量

e.g.

$ declare -i n m num  # 将 n, m, num 声明为整型
$ declare -r CONST=23 # 将 CONST 声明为只读,等价于 readonly CONST

$ declare -p CONST    # 显示变量的属性和值
declare -r CONST="23"

重定向

文件描述符

为了表示并区分已经打开的文件,Linux 会给每个文件分配一个 ID,即文件描述符(File Descriptor)。stdin,stdout,stderr 是默认打开的,它们已经有了自己的文件描述符:

FD 文件名
0 stdin
1 stdout
2 stderr

Linux 始终从文件描述符 0 读取内容,向文件描述符 1 输出正确结果,向文件描述符 2 输出错误提示。

文件描述符操作符(>, <)可以修改文件描述符指向的文件,从而实现重定向的功能。

输出重定向

格式:FD>file

在输出重定向中,> 表示覆盖,>> 表示追加。

💡 Tip: FD> 之间不能有空格,否则 Shell 会解析失败;>file 之间的空格可有可无。

command >>file           # 以追加输出的方式打开 file,并让文件描述符 1 指向 file。这里 1 被省略
command 2>>file
command >>file1 2>>file2 # 正确结果输出到 file1,错误提示输出到 file2
command >>file 2>&1      # 2>&1:让文件描述符 2 指向文件描述符 1 所指的文件。结果是 1 和 2 都指向 file
command &>file           # 以覆写输出的方式打开 file,并让文件描述符 1 和 2 都指向 file
command n>&-             # 关闭文件描述符 n 及其代表的文件。n 默认为 1

建议将正确结果和错误输出保存到不同文件

/dev/null 文件

如果不想显示或保存任何输出,可以将输出重定向到 /dev/null 文件中:

ls > /dev/null   # 仅抛弃标准输出
ls 2> /dev/null  # 仅抛弃错误输出
ls &> /dev/null  # 抛弃所有输出
dir > NUL  # CMD,仅抛弃标准输出
dir 2>NUL  # CMD,仅抛弃错误输出
dir > NUL 2>&1  # CMD,抛弃所有输出;2>&1 用于将标准错误重定向到标准输出,从而同时抛弃标准输出和标准错误。
Get-ChildItem | Out-Null  # PowerShell,仅抛弃标准输出

Get-ChildItem > $null      # PowerShell,仅抛弃标准输出
Get-ChildItem 2> $null     # PowerShell,仅抛弃错误输出
Get-ChildItem *> $null     # PowerShell,抛弃所有输出

输入重定向

格式:FD<file

command <file          # 以输入的方式打开 file,并让文件描述符 0 指向 file。这里 0 被省略
command <input >output # 从 input 输入,正确结果输出到 output
command n<>file        # 同时以输入和输出的方式打开 file,并让文件描述符 n 指向 file。n 默认为 0

# 使用 Here Document
command <<EOF
  document
EOF

Here Document

语法:

command <<END  # END 是分界符,可以自定义
  document
END  # 行内不能有其他内容

Shell 将把 document 的内容输入到命令中。

忽略命令替换

默认情况下,document 中出现的变量和命令也会被求值或运行。你可以将分界符用单引号或双引号包围来使 Shell 替换失效:

command <<'END'
  document
END
忽略制表符

默认情况下,行首的制表符也被当做正文的一部分。不过我们输入制表符仅仅是为了格式对齐,并不希望它作为正文的一部分。为了去掉制表符,你可以在 <<END 之间添加 -

cat <<-END
  document
END
常见用法

Here Document 最常用的功能还是向用户显示命令或者脚本的用法信息:

usage(){
  cat <<-END
    usage: command [-x] [-v] [-z] [file ...]
    A short explanation of the operation goes here.
    It might be a few lines long, but shouldn't be excessive.
END
}

代码块重定向

将重定向命令放在代码块的结尾处,就可以对代码块中的所有命令实施重定向:

sum=0
while read n; do
  ((sum += n))
done <input >output  # 输入重定向为 input,输出重定向为 output 。

# 组命令重定向
{
  echo "line 1"
  echo "line 2"
  echo "line 3"
} >file

管道

command1 | command2  # 管道符 | 左边命令的 stdout 将连接到右边命令的 stdin

注意管道只处理正确的输出(stdout),如果想让管道也处理错误输出(stderr),需要将 stderr 重定向到 stdout: command1 2>&1 | command2

管道数据流指示

curl -sSL https://install.python-poetry.org | python3 -  # 从网络获取安装脚本,然后执行

python3 - 表示 Python 将从管道读取输入

Runoob: Shell 中的特殊字符

管道与输入重定向

command1 <input | command2 | command3 >output  # 第一个命令从 input 获取输入,最后一个命令向 output 写入输出。

C 语言中文网:管道详解

过滤器

过滤器:从标准输入读取数据,向标准输出输出结果的命令

常用过滤器:

命令 说明
awk 用于文本处理的解释性程序设计语言,通常被作为数据提取和报告的工具。
cut 用于将每个输入文件(如果没有指定文件则为标准输入)的每行的指定部分输出到标准输出。
grep 用于搜索一个或多个文件中匹配指定模式的行。
tar 用于归档文件的应用程序。
head 用于读取文件的开头部分(默认是 10 行)。如果没有指定文件,则从标准输入读取。
paste 用于合并文件的行。
sed 用于过滤和转换文本的流编辑器。
sort 用于对文本文件的行进行排序。
split 用于将文件分割成块。
strings 用于打印文件中可打印的字符串。
tac cat 命令的功能相反,用于倒序地显示文件或连接文件。
tail 用于显示文件的结尾部分。
tee 用于从标准输入读取内容并写入到标准输出和文件。
tr 用于转换或删除字符。
uniq 用于报告或忽略重复的行。
wc 用于打印文件中的总行数、单词数或字节数。

grep

grep 'word' file    # 在 file 中查找并显示包含 word 的行
grep -i 'word' file # 忽略大小写
grep -R 'word' .    # 在当前目录及其子目录下的所有文件中查找并显示包含 word 的行
grep -c 'word' file # 搜索并显示 word 在 file 中出现的次数

Shell 结构语句

if else

if condition  # 也可以写作:if condition; then
then
  statements
else
  statements
fi

💡 Tip: condition 检测的是命令的退出状态。通常退出状态为 0 表示 “成功” ,其他状态表示 “失败” 。((1)) 的退出状态为 0 。

if elif else

if condition1; then
  statements
elif condition2; then
  statements
elif condition3; then
  statements
else
  statements
fi

if 语句也支持使用逻辑运算符将多个退出状态组合起来。

case in

语法:

case expression in  # expression 可以是变量、数学表达式,或者是命令的执行结果。只要能得到 expression 的值就可以
  pattern1)         # pattern 可以是数字,字符串,甚至是简单的正则表达式
    statements
    ;;              # 两个分号表示一条 case 语句的结束
  pattern2)
    statements
    ;;
  pattern3)
    statements
    ;;
  *)            # 可以没有 *) 部分。* 实际上是正则表达式
    statements  # 最后的双分号可以省略
esac

case in 支持的正则表达式

格式 说明
* 表示任意字符串
[abc] 表示 a, b, c 三个字符中的任意一个。比如 [15ZH] 表示 1, 5, Z, H 中的任意一个
[m-n] 表示从 m 到 n 的任意一个字符。比如 [0-9] 表示一个数字,[0-9a-zA-Z] 表示一个字母或数字
| 表示多重选择,相当于或运算。比如 abc|xyz 表示匹配字符串 "abc" 或 "xyz"

e.g.

read -n 1 char
case $char in
  [a-zA-Z])
    printf "\n字母\n"
    ;;
  [0-9])
    printf "\n数字\n"
    ;;
  [,.?!])
    printf "\n标点符号\n"
    ;;
  *)
    printf "\n错误\n"
esac

select in

select in 可以自动显示带编号的菜单,用户输入编号就可以为循环变量赋予编号对应的值。select in 经常与 case in 一起使用。

语法(结合 case in):

select variable in value_list
do
  case variable in
    pattern1)
      statements
      break        # select in 只有遇到 break 或文件结束符才会退出循环
      ;;
    pattern2)
      statements
      break
      ;;
    pattern3)
      statements
      break
      ;;
    *)
      echo "输入错误,请重新输入。"
  esac
done

可以修改环境变量 PS3 来自定义输入提示符

break,continue

break n
continue n

其中 n 表示作用的循环的层数(从内向外)。

while

语法:

while condition
do
  statements
done
while true; do
  statements
done

until

until 的使用场景很少,一般使用 while 即可。

语法:

until condition # 若 condition 不成立则执行循环语句
do
  statements
done

for

C 风格 for

sum=0
for((i=1; i<=5; ++i))
do
  ((sum += i))
done
echo $sum

Python 风格 for in

for variable in value_list
do
  statements
done
value_list
  1. 直接给出具体的值:1 2 3 4 5"john" "jack" "tom"
  2. 给出一个取值范围(只支持数字和字母):{1..5}{a..f}
  3. 使用命令的执行结果:$(ls)
  4. 使用 Shell 通配符:*.sh
  5. 使用特殊变量:$@(如果省略 value_list 的话相当于使用 $@)

Shell 通配符

[[#特殊变量]]

函数

语法:

# 定义
function func() {  # function 关键字可以省略。如果写了 function 关键字,那么 () 可以省略
  statements
  [return value]
}

function func {  # 省略 ()
  ...
}

func() {  # 省略 function
  ...
}

function func --description "some description"  # 你可以为函数添加描述

# 调用
func [param1] [param2] [...] # 函数名后面不加括号

Shell 不限制定义和调用的顺序。

函数返回值

Shell 中的函数返回值是一个介于 0~255 的整数,用来表示函数的退出状态:返回值为 0 表示函数执行成功,非 0 表示函数执行失败。函数执行失败时可以根据退出状态来判断具体出现了什么错误。

💡 Tip: 在 Shell 中不要用返回值表示函数的处理结果

如果函数体中没有 return 语句,那么函数将使用最后一条命令的退出状态作为自己的返回值。

如何得到函数的处理结果

要想得到函数的处理结果,可以使用以下两种方案:

  • 在函数内部使用 echoprintf 等命令将结果输出,并在函数外部使用 $()`` 捕获结果。(推荐)
  • 将处理结果赋给全局变量

条件表达式

[[ ]] 用来检测某个条件是否成立。

[[ ]] 是 Shell 内置关键字,不是命令,在使用时没有给函数传递参数的过程。因此相比 test 命令,[[ ]] 不需要关注某些细枝末节:

  • 不需要把变量名用双引号 "" 包围起来。即使变量是空值,也不会出错。
  • 不需要对 >< 进行转义。

语法:

[[ expression ]]  # 注意两侧的空格

文件检测操作符

expression 作用
-e path 判断 path 是否存在
-d path 判断 path 是否存在,并且是否为目录文件
-f path 判断 path 是否存在,并且是否为普通文件
-s path 判断 path 是否存在,并且是否非空
-r path 判断 path 是否存在,并且是否拥有读权限
-w path 判断 path 是否存在,并且是否拥有读权限
-x path 判断 path 是否存在,并且是否拥有执行权限
path1 -nt path2 判断 path1 的修改时间是否比 path2 新
path1 -ot path2 判断 path1 的修改时间是否比 path2 旧
read fileName
read msg

if [[ -w $fileName ]] && [[ -n $msg ]]; then
  echo $msg >$fileName
  echo "写入成功"
else
  echo "写入失败"
fi

使用 ! 的情况:

if [[ ! -e $path ]]; then
  echo "$path 不存在"
fi

# 不要写成下面这样
if ![[ -e $path ]]
# 或者
if [[ !(-e $path) ]]

字符串判断操作符

expression 作用
-z str 判断 str 是否为空
-n str 判断 str 是否非空
str1 = str2
str1 == str2
判断 str1 是否和 str2 相等
str1 != str2 判断 str1 是否和 str2 不相等
str1 > str2 判断 str1 是否大于 str2
str1 < str2 判断 str1 是否小于 str2

💡注意:在使用 ===!=>< 运算符比较字符串时,运算符与两边的字符串之间一定要有空格!否则可能会出现奇葩问题。

read str1
read str2

# 检测字符串是否为空
# 条件表达式支持逻辑运算符
if [[ -z $str1 || -z $str2 ]]; then
  echo "字符串不能为空"
# 比较字符串
elif [[ $str1 < $str2 ]]; then
  echo "str1 < str2"
else
  echo "str1 >= str2"
fi

条件表达式支持正则表达式

可以使用 =~ 来检测字符串是否符合某个正则表达式:

# 检测一个字符串是否是手机号
read tel
if [[ $tel =~ ^1[0-9]{10}$ ]]; then
  echo "你输入了一个手机号"
else
  echo "你输入的不是手机号"
fi

更多操作符请参见 C 语言中文网

echo 输出彩色字符

echo -e "\033[背景色;前景色m输出文字"

echo -e "\e[31m输出文字" # 只指定前景色,输出红色字符
echo -e "\e[41m输出文字" # 只指定背景色,输出红底白字
echo -e "\e[0m输出文字"  # 使用默认配色

# 样例
echo -e "\e[31mHello, World\e[0m" # 红色 Hello, World
# 注意,在 -e 选项的 echo 命令中,输出语句如果含有感叹号 !,则感叹号的后面只能是空白符或语句结束的双引号。否则 ! 会被解析成事件提示符
echo -e "文字"'!'"文字" # 解决 echo -e 输出 ! 的问题

# 设置颜色变量
GREEN="\e[32m"
RES="\e[0m"

echo -e "${GREEN}Hello, World$RES"

# 设置颜色动作
PRT_GREEN="echo -e \e[32m"

${SET_GREEN}"message"$RES
背景色 前景色 颜色
40 30 黑色
41 31 红色
42 32 绿色
43 33 黄色
44 34 紫色
45 35 粉色
46 36 蓝色
47 37 灰色

\033[背景色;前景色m 是转义序列,其中 \033[ 是转义起始符,m 是转义终止符。

\033 对应 ASCII 码表的 Esc,可以用 \e\E 代替。

背景色和前景色没有先后顺序。适应我们惯常的思维顺序,一般先确定背景色,再确定前景色。

C 语言中文网:echo 命令:显示文字并给文字添加颜色

事件提示符

!100  # 执行第 100 条命令
!-1   # 执行倒数第一条命令
!!    # !-1 的 alias

!abc  # 引用最近的以 abc 开头的命令。可以在运行一条命令之后忘记这个命令的参数时使用。
!?abc # 引用最近的包含 abc 的命令

# 间接取值
$ a="Hello"
$ b="a"
$ echo ${!b}
Hello

在 Shell 中,使用 ⬆️ 和 ⬇️ 也可以快速切换上一条、下一条命令。

Linux 多命令顺序执行连接符

| 符号 | 作用 |
| :--: | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| ; | 没有任何逻辑关系的连接符。当多个命令用分号连接时,各命令之间的执行成功与否彼此没有任何影响,都会一条一条执行下去。 |
| | | | 逻辑或,当用此连接符连接多个命令时,前面的命令执行成功,则后面的命令不会执行。前面的命令执行失败,后面的命令才会执行。 |
| && | 逻辑与,当用此连接符连接多个命令时,前面的命令执行成功,才会执行后面的命令,前面的命令执行失败,后面的命令不会执行 |
| | | 管道符,当用此连接符连接多个命令时,前面命令执行的正确输出,会交给后面的命令继续处理。若前面的命令执行失败,则会报错,若后面的命令无法处理前面命令的输出,也会报错。 |

查找历史记录

history

对于 Zsh 用户,可以使用 fc 命令来搜索历史记录,它是 history 命令的增强版本:

fc -l 1

这里 -l 选项意味着列出历史条目,1 是从历史记录的第一条命令开始搜索。

最后,如果你在 Zsh 中启用了增强的历史搜索功能,也可以直接通过按下特定的快捷键(如 Ctrl + R)进入增量式搜索模式。例如:

  1. 在命令行提示符下按下 Ctrl + R

  2. 然后开始输入你要搜索的命令片段,如 cd

    (reverse-i-search)`cd': cd /path/to/directory
    

在增量式搜索模式下,你可以继续输入来精确搜索,或者按下 Ctrl + R 来查看匹配项的上一个条目。按下回车即可执行搜索结果中的命令,或按下 Esc 或者 Ctrl + G 来退出搜索模式而不执行任何命令。

posted @ 2024-08-15 01:21  Undefined443  阅读(28)  评论(0编辑  收藏  举报