第一部分:基础

1. Bash 简介

1.1 Shell 是什么

Shell 是计算机操作系统中的一种用户界面,它为用户提供了与操作系统内核进行交互的方式。通过 Shell,用户可以执行命令、运行程序、管理文件系统等。Shell 是一个解释性的编程语言,它接受用户输入的命令,并将其转换为操作系统可以理解的指令。

1.2 为什么使用 Shell?

  • 命令行的强大性: Shell 提供了强大的命令行接口,使得用户可以通过简短的命令完成复杂的任务。
  • 脚本编程: 使用 Shell 脚本,用户可以编写一系列的命令,形成可重复执行的脚本,实现自动化和批量处理。
  • 系统管理: 系统管理员和开发人员常常使用 Shell 来管理和配置操作系统,执行系统维护任务。
  • 灵活性和定制性: Shell 具有高度的灵活性,用户可以根据需要定制环境,创建别名,以及编写自定义函数。

1.3 Bash 是什么

  • Bourne Shell: Bash(Bourne Again SHell)是 Bourne Shell 的一个扩展。Bourne Shell 是由 Stephen Bourne 开发的原始 Unix Shell。
  • GNU 项目: Bash 是 GNU 项目的一部分,在 1989 年由 Brian Fox 开发,目的是作为自由软件基金会 GNU 工具集的一部分。
  • 标准 Shell: Bash 是许多 Linux 发行版和 macOS 默认的标准 Shell(macos从13开始默认转向zsh),也是许多脚本和系统管理任务的首选工具。

2. Bash 的基本语法

2.1 命令行结构

Bash命令行的基本结构包括命令、选项、参数和控制操作符。

1
command [options] [arguments]
  • 命令(command(命令)): 要执行的实际命令或程序的名称。例如,lscpecho等都是命令。
  • 选项(options): 有时也称为标志或开关,用于修改命令的行为。选项通常以单个破折号(-)或双破折号(--)开始。例如,ls -l中的-l是一个选项。
  • 参数(arguments): 传递给命令的输入数据或操作对象。参数是命令的具体操作目标。例如,cp source destination中的sourcedestination就是参数。

2.2 变量

在Bash中,变量是用来存储数据值的符号名称。不同于许多其他编程语言,Bash 并不区分变量的类型。本质上说,Bash 变量是字符串,但在某些情况下,Bash 允许对变量进行算术运算和比较。

变量可以存储各种类型的数据,包括字符串、数字和数组等。变量的命名遵循一些规则:

  • 变量名是大小写敏感的。
  • 变量名可以包含字母、数字和下划线,但不能以数字开头。
  • 通常,使用大写字母表示环境变量,而使用小写字母表示用户定义的变量
2.2.1 变量赋值
  • 简单赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
variable_name=value  #等号两边不能有空格,如果有空格则认为空格后面是命令的参数,执行错误

➜ ~ age=37
➜ ~ email="$name@163.com"
➜ ~ echo $name $age
beizi 37
➜ ~ echo $name $age $email
beizi 37 beizi@163.com

# 双引号内的文本允许变量扩展和命令替换。单引号内的文本会被视为字面字符串,不进行变量扩展或命令替换。
➜ ~ email2='$name@163.com'
➜ ~ echo $email2
$name@163.com

  • 使用命令输出赋值 (命令替换)
1
2
3
4
5
6
7
8
9
var=$(command) #或
var=`command`

➜ ~ dir=$(pwd)
➜ ~ echo $dir
/Users/bz
➜ ~ dir=`pwd`
➜ ~ echo $dir
/Users/bz
  • 环境变量赋值

可以使用export命令将变量声明为环境变量,使其在脚本中和子进程中可用。

1
export MY_VARIABLE="some_value"
  • 直接读取用户输入赋值

使用read命令可以直接从用户输入中获取值并赋给变量。

1
2
3
4
5
6
7
8
9
10
11
12
➜  ~ echo "Enter your name:"
Enter your name:
➜ ~ read name
beizi
➜ ~ echo "Your name is $name"
Your name is beizi

#当然使用-p选项提示用户信息,而不用单独echo提示信息,-s隐藏用户输入信息,供密码等信息;-t设置超时时间
➜ ~ read -p "Enter you name:" name
Enter you name:beizi
➜ ~ echo $name
beizi

read 也可以同时给多个变量赋值,多个变量之间用空格分隔

1
2
3
4
5
6
7
8
9
10
➜  ~ read name age 
beizi 100
➜ ~ echo $name $age
beizi 100

# read的-a选项可以给数组赋值,值之间用空格分隔
➜ ~ read -p "Enter the animals: " -a animals
Enter the animals: cat dog pig cow horse
➜ ~ echo ${animals[@]}
cat dog pig cow horse
  • 数组赋值
1
2
3
4
5
my_array=("value1" "value2" "value3")

➜ ~ animas=(pig dog cat cow horse)
➜ ~ echo ${animals[@]}
cat dog pig cow horse
  • 默认值和替代值

可以使用${undefined_var:-default_value}来为变量设置默认值,如果变量未设置,则使用默认值。

1
2
3
4
5
6
7
8
9
export LOG_DIR="${LOG_DIR:-/var/log}" # 假设有一个环境变量LOG_DIR表示日志目录,可以这样设置默认值

input_file="${1:-default_input.txt}" # 这将使用脚本的第一个参数作为输入文件名,如果没有提供参数,则使用 default_input.txt 作为默认值。

message="User input: ${user_input:-No input provided}" #检查变量是否为空,为空时提供一个默认值
echo $message

# 配置文件中读取变量,如果未定义,则使用默认值
timeout="${config_timeout:-30}"
  • 一次为多个变量赋值

可采用read 和here stings方式或<方式为多个变量赋值

1
2
3
4
5
6
7
8
➜  ~ read name age <<< "zs 37"
➜ ~ echo $name $age
zs 37
# 也可以通过readarray方式赋值给数组
➜ ~ read -a file_name <<< $(ls)
➜ ~ echo ${file_name[@]}
command command.lsp test1.lsp test2.lsp test3.lsp

  • 间接赋值

使用间接引用可以通过变量的值构建变量名,这对于动态引用变量很有用。

1
2
3
4
5
6
7
8
9
10
11
12
bash-3.2$ var_name=age
bash-3.2$ age=37
bash-3.2$ echo ${!var_name}
37
# 使用eval命令可以执行字符串构成的命令,将字符串解释成命令执行
bash-3.2$ read -p "enter the name: " name
enter the name: beizi
bash-3.2$ read -p "enter the age: " age
enter the age: 37
bash-3.2$ eval "$name=$age"
bash-3.2$ echo "$name's age is set to ${!name}"
beizi's age is set to 37
2.2.2 引用变量
  • 通过使用”$”符号来实现变量引用
1
2
3
➜ ~ name="beizi"
➜ ~ echo $name
beizi
  • 使用花括号括起变量,可明确变量的边界
1
2
➜ ~ echo "${name}_age is $age"
beizi_age is 37

双引号中的变量会进行变量替换,而单引号则不会,会保留字符串的字面量。

使用${!var}间接引用变量,将使用var的值作为变量名引用。

  • 命令替换

使用$(command)或反引号``可以将命令的输出赋值给变量。

  • 位置参数

在脚本中,可以通过$1$2$3等位置参数引用脚本的参数

2.2.3 只读变量

可以使用readonly命令使变量变为只读,一旦变为只读,其值将不能被修改。

1
readonly hostname
2.2.4 删除变量

使用unset命令可以删除一个变量。

1
unset hostname
2.2.5 特殊变量

Bash Shell中有一些特殊的变量,它们具有特殊的含义。例如:

  • $0:脚本名称
  • $1, $2, …:脚本参数
  • $#:脚本参数的个数
  • $@: 传递给脚本或函数的所有参数的列表。
  • $*: 传递给脚本或函数的所有参数作为单个字符串,不保留参数之间的空格。
  • $?:上一个命令的退出状态,执行成功$?的值为0,否则为非零值。
  • $!:后台运行的最后一个作业的进程ID。
  • $IFS: 内部字段分隔符,用于指定字段之间的分隔符,默认为包含空格、制表符和换行符的字符串。
2.2.6 环境变量

环境变量是操作系统或特定程序所使用的动态值,其值可以在操作系统级别或在特定进程中设置。这些变量对于控制系统行为、提供配置选项以及与不同程序之间共享信息非常重要。以下是环境变量的一些详细介绍:

  1. 作用

    • 环境变量提供了一种在操作系统和应用程序之间共享配置信息的标准方式。
    • 它们允许您更改系统的行为,例如更改默认编辑器、设置语言首选项等。
  2. 设置环境变量

    • 在 Unix/Linux 系统中,可以通过在 shell 中使用 export 命令来设置环境变量。例如:export PATH=/usr/local/bin:$PATH
    • 在 Windows 中,可以通过控制面板或命令行使用 set 命令设置环境变量。例如:set PATH=C:\Program Files\MyApp;%PATH%
  3. 常见环境变量

    在 Linux 系统中,有许多常见的环境变量,它们用于控制系统行为、配置应用程序和提供系统信息。以下是一些常见的 Linux 环境变量:

    • PATH:用于指定系统在哪些目录中查找可执行文件。这是一个由冒号分隔的目录列表。

    • HOME:指定当前用户的主目录路径。

    • USER:指定当前登录的用户名。

    • LANG:指定系统默认的语言环境,影响字符编码和语言设置。

    • PWD:指定当前工作目录的路径。

    • SHELL:指定当前用户的默认 shell 的路径。

    • TERM:指定当前终端的类型,影响终端的显示行为。

    • PS1:指定 shell 提示符的格式。

    • LD_LIBRARY_PATH:用于指定动态链接器在哪些目录中查找共享库文件。

    • DISPLAY:指定 X 窗口系统的显示服务器。

    • EDITOR:指定系统默认的文本编辑器。

    • TMPDIR:指定临时文件目录的路径。

    • TZ:指定系统的时区。

    • **LC_***:一系列环境变量,用于指定系统的本地化设置,如 LC_COLLATELC_NUMERICLC_TIME 等。

    这些环境变量在 Linux 系统中广泛使用,对于系统配置、用户交互和程序运行都有重要作用。可以使用 env 命令来查看当前 shell 中的所有环境变量,或者使用 echo $VARIABLE_NAME 来查看特定环境变量的值。

  4. 环境变量的继承

    • 当一个进程创建子进程时,子进程会继承父进程的环境变量。
    • 当您在 shell 中设置环境变量时,只有当前 shell 及其子 shell 可以访问该变量。
  5. 用途

    • 控制系统行为:例如,LANG 环境变量用于设置系统的语言环境。
    • 配置应用程序:例如,PATH 环境变量用于指定系统中的可执行文件目录。
    • 共享信息:例如,某些应用程序可能会使用特定环境变量来确定配置文件的位置或其他重要信息。
  6. 查看环境变量

    • 在 Unix/Linux 中,可以使用 env 命令查看当前 shell 中的所有环境变量。
    • 在 Windows 中,可以使用 set 命令查看当前 shell 中的所有环境变量。

总的来说,环境变量在操作系统和应用程序中起着重要作用,它们提供了一种在系统中配置和共享信息的灵活方式。

2.2.7 变量的作用域

在bash中,变量的作用域有两种:全局作用域和局部作用域。

  • 在脚本最外层定义的变量具有全局作用域,任何地方都能访问这些全局变量,包括在函数内部。这些变量在整个脚本中都是可见的。
1
2
3
4
5
6
7
8
9
#!/bin/bash
name="beizi"

function sayhi {
# 函数内部访问全局变量
echo "Hello ${name}"
}
# 在脚本中调用函数
sayhi
  • 在函数内使用 local 定义的变量具有局部作用域,只能在函数内访问,无法在函数外部直接访问。函数执行完时,局部变量的生命周期也结束了。
1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash
name="beizi"
function sayhi {
echo "Hello ${name}"
# 定义局部变量
local age=37
echo "my age is ${age}"
}
sayhi
# 在脚本中调用局部变量失败
echo "your name is ${age}"

需要注意,如果在函数内部不使用 local 关键字声明变量,那么该变量将具有全局作用域,即使它是在函数内部定义的。使用 local 关键字可以确保变量在函数内部是局部的,而在函数外部是不可见的。

变量的作用域对于避免命名冲突、保持代码清晰和维护性非常重要。确保在需要的情况下使用局部变量,以避免不必要的全局变量。

2.3 数组

在Bash中,数组是一种有序集合,可以存储多个元素,并通过索引来访问这些元素。Bash支持一维数组,数组的元素可以包含字符串或数字。

2.3.1 声明数组

使用以下语法可声明数组:

1
arr=("element1" "element2" "element3")
2.3.2访问数组元素

通过索引可以访问数组元素,索引从0开始

1
2
echo ${arr[0]} # 输出第一个元素
echo ${arr[1]} # 输出第二个元素
2.3.3 数组长度:

获取数组的长度可以使用 ${#array[@]}

1
echo "arr length: ${#arr[@]}"
2.3.4 追加元素:

可以使用括号和+=操作符来追加元素到数组:

1
arr+=("new_element")
2.3.5 删除数组
2.3.6 遍历数组
  • 可以使用循环来遍历数组中的所有元素:
1
2
3
for i in "${arr[@]}"; do
echo $i
done
  • 使用!间接引用遍历数组
1
2
3
4
5
animals=("dog" "cat" "pig" "horse")

for i in "${!animals[@]}"; do
echo "$i: ${animals[$i]}"
done
  • 还可以使用 C 风格的 for ((i = 0; i < ${#array[@]}; i++)); do ... done 循环来遍历数组元素:
1
2
3
4
5
animals=("dog" "cat" "pig" "horse")

for ((i=0; i < ${#animals[@]};i++));do
echo ${animals[i]}
done
  • 使用while循环遍历数组:

可以使用 while 循环来遍历数组:

1
2
3
4
5
6
animals=("dog" "cat" "pig" "horse")

while [ ${#animals[@]} -gt 0 ];do
echo "${animals[0]}"
animals=("${animals[@]:1}")
done

可以使用whileshift命令遍历并删除数组中的元素:

1
2
3
4
5
6
7
8
animals=("dog" "cat" "pig" "horse")

while [ ${#animals[@]} -gt 0 ]; do
echo "${animals[0]}"
# 使用 shift 命令移除数组的第一个元素
# 注意:shift 会修改原数组,将数组的第一个元素移除,使下一个元素成为新的第一个数组。
shift
done
2.3.7 关联数组

Bash 4.0及以上版本支持关联数组,可以使用字符串作为索引:

1
2
3
4
5
6
7
8
9
10
# 使用declare -A 声明关联数组
declare -A assoc_array
# 初始化关联数组
assoc_array["key1"]="value1"
assoc_array["key2"]="value2"

#遍历关联数组
for key in "${!assoc_array[@]}"; do
echo "$key: ${assoc_array[$key]}"
done

2.4 字符串操作

在Bash中,有很多用于字符串操作的内建功能。参数扩展操作符是一种用于对变量值进行处理的机制,可以在变量替换时使用。以下是常用的参数扩展操作符:

在 Bash 中,参数扩展操作符是一种用于对变量值进行处理的机制,可以在变量替换时使用。以下是常用的参数扩展操作符:

  1. **${parameter}**:变量的值。
  2. **${parameter:-word}**:如果变量 parameter 已设置且非空,则返回其值,否则返回 word
  3. **${parameter:=word}**:如果变量 parameter 已设置且非空,则返回其值,否则将 parameter 设置为 word,并返回 word
  4. **${parameter:+word}**:如果变量 parameter 已设置且非空,则返回 word,否则返回空字符串。
  5. **${parameter:offset:length}**:从变量 parameter 的值中提取子字符串,从 offset 位置开始,长度为 length
  6. ${!prefix*} 或 **${!prefix@}**:匹配以 prefix 开头的变量名,返回这些变量名组成的列表。
  7. **${#parameter}**:返回变量 parameter 的长度。
  8. ${parameter#word} 和 **${parameter##word}**:从变量 parameter 的值中删除匹配 word 的最短或最长前缀。
  9. ${parameter%word} 和 **${parameter%%word}**:从变量 parameter 的值中删除匹配 word 的最短或最长后缀。
  10. ${parameter/pattern/string} 和 **${parameter//pattern/string}**:将变量 parameter 的值中匹配 pattern 的部分替换为 string,只替换一次或全部替换。

这些参数扩展操作符提供了丰富的功能,可用于对变量值进行各种处理和操作。使用这些操作符可以方便地进行字符串处理、条件判断等操作。

  1. ${parameter^^} 和 **${parameter,,}**:将变量 parameter 的值转换为大写或小写。

  2. **${parameter@operator}**:执行数组操作符,如 Q 表示将数组转换为引用列表,A 表示将数组转换为分配列表,E 表示对数组进行扩展,P 表示对数组进行排序。

  3. **${parameter:?[error_message]}**:如果变量 parameter 未设置或为空,则输出错误信息并退出。

  4. **${parameter:?}**:如果变量 parameter 未设置或为空,则输出标准错误信息并退出。

  5. ${parameter^} 和 **${parameter,}**:将变量 parameter 的值中的首字符转换为大写或小写。

  6. ${parameter^pattern} 和 **${parameter,pattern}**:将变量 parameter 的值中匹配 pattern 的部分的首字符转换为大写或小写。

  7. ${parameter^^pattern} 和 **${parameter,,pattern}**:将变量 parameter 的值中匹配 pattern 的部分全部转换为大写或小写。

  8. **${parameter@Q}**:将变量 parameter 的值转换为引用列表(单词间用引号分隔)。

  9. **${parameter@A}**:将变量 parameter 的值转换为分配列表(单词间用空格分隔)。

  10. **${parameter@E}**:对数组 parameter 进行扩展,返回分配列表。

  11. **${parameter@P}**:对数组 parameter 进行排序。

  12. ${parameter#*} 和 **${parameter##*}**:从变量 parameter 的值中删除第一个或最后一个 * 之前的内容。

  13. ${parameter%*} 和 **${parameter%%*}**:从变量 parameter 的值中删除第一个或最后一个 * 之后的内容。

  14. **${parameter/pattern}**:删除变量 parameter 的值中第一个匹配 pattern 的部分。

  15. **${parameter//pattern}**:删除变量 parameter 的值中所有匹配 pattern 的部分。

  16. **${parameter/#pattern}**:如果变量 parameter 的值以 pattern 开头,则删除匹配部分。

  17. **${parameter/%pattern}**:如果变量 parameter 的值以 pattern 结尾,则删除匹配部分。

2.4.1 常用基本操作
  • 获取字符串长度:${#string}
  • 子字符串提取:${string:start:length}
  • 删除子字符:

${variable#pattern}:从变量的开头删除最短匹配 pattern 的子字符串。
${variable##pattern}:从变量的开头删除最长匹配 pattern 的子字符串。
${variable%pattern}:从变量的结尾删除最短匹配 pattern 的子字符串。
${variable%%pattern}:从变量的结尾删除最长匹配 pattern 的子字符串。

  • 查找子字符串:

    [[ $string == *"substring"* ]] 测试是否包含子字符串

  • 替换子字符串:

    ${variable/pattern}:不会返回匹配的部分,而是将匹配的部分替换为空字符串,并返回结果。这是一种替换操作,而不是查找操作

    替换第一个匹配的字符${string/pattern/replacement}

    替换所有匹配的字符${string//pattern/replacement}

  • 转换大小写:${string,,},${string^^}

2.4.2 字符串比较和判定
  • 判断空值:[ -z "$string" ]
  • 判断非空值 [ -n "$string" ]
  • 字符串比较:[[ $string1 == $string2 ]]
  • 字符串长度比较:[ "${#string1}" -eq "${#string2}" ]

2.5 数值运算

在 Bash 中,进行整数运算有几种方式,其中最常用的方式是使用双括号 ((...))expr 命令。

  • 双括号 ((...)) 是 Bash 中进行整数运算的一种常见方式。它支持各种算术运算符,如加法、减法、乘法、除法等。
1
2
3
4
num1=10
num2=5
result=$((num1 + num2))
echo "The result is: $result"
  • expr 命令用于在 Bash 中进行基本的整数运算。它采用与 C 语言相似的语法。
1
2
3
4
num1=10
num2=5
result=$(expr $num1 + $num2)
echo "The result is: $result"
  • let 命令是用于在 Bash 中进行整数运算的另一种方法。
1
2
3
4
num1=10
num2=5
let "result = num1 + num2"
echo "The result is: $result"

在早期版本的 Bash 中,支持使用 $[...] 进行整数运算,例如:

1
2
result=$[10 + 5]
echo "The result is: $result"

这种语法是一种旧式的整数运算语法,在较新版本的 Bash 中已经不再推荐使用,因为它已经被双括号 ((...)) 语法取代。使用 $[...] 的方式在某些情况下可能会导致不同版本的 Bash 兼容性问题。

推荐使用更现代的双括号 ((...)) 语法进行整数运算,因为它更加直观和易读,并且不会产生兼容性问题。

注意事项

  • 这些方法都仅支持整数运算,不支持浮点数运算。
  • 在进行算术运算时,变量名前面不需要加上 $ 符号。
  • 如果要在脚本中进行大量的数学运算,((...)) 通常是最常用的方法,因为它更直观、易读,并且不需要额外的命令。
  • 使用 expr 命令时需要注意,表达式中的运算符和操作数之间需要用空格隔开,而且特殊字符(如 *)需要转义或用引号括起来,否则可能会导致意外的结果。

以上所有的数值运算都是在整数范围内进行的。如果你需要进行浮点数运算,可以使用 bc 命令,它是一个用于数学计算的命令行工具,其名称来源于 “Basic Calculator”。例如:

1
2
3
result=$(echo "scale=2; 10 / 3" | bc)
echo "The result of division is: $result"
# 这将输出 3.33。 scale=2 用于指定结果保留两位小数。
1
2
3
4
5
6
7
calculate() {
echo "$1" | bc -l
}

result=$(calculate "10 + 5")
echo "The result is: $result"

1
2
3
4
5
6
7
8
9
bc
# 这会启动 bc,允许你在交互模式下输入数学表达式并进行计算。要退出 bc,只需输入 quit 或按下 Ctrl + D。

# 执行文件中的表达式
bc -l filename # -l是了加载标准数学函数库

# 指定小数位数
bc -l
scale=2
1
2
3
4
5
6
7
8
echo "5 + 3" | bc
# 这个命令会将字符串 "5 + 3" 传递给 bc 进行计算,并将结果输出到标准输出。这对于将 bc 嵌入到脚本或管道中很有用。

echo "obase=16; 10" | bc
# 这个命令将十进制数 10 转换为十六进制。在 bc 中,你可以使用 ibase 和 obase 变量来指定输入和输出的进制数。

# 这个命令会从 inputfile 文件中读取 bc 表达式,并将结果写入 outputfile 文件。
bc -l < inputfile > outputfile

bc 遇到错误时,会输出错误消息并退出。你可以通过捕获 stderr 来处理错误。

bc中一些常用的数学函数

三角函数

  • s():正弦函数。
  • c():余弦函数。
  • a():反正弦函数。
  • l():反余弦函数。
  • e():正切函数。
  • j():反正切函数。

对数函数

  • l():自然对数函数(以 e 为底)。
  • e():以 10 为底的对数函数。

指数函数

  • e():自然指数函数。
  • ^:幂函数。

其他

  • sqrt():平方根函数。
  • scale:设置小数点后的精度。

2.6 注释和文档

在Shell脚本中,您可以使用注释和文档来提高代码的可读性和可维护性。

2.6.1 注释

在Shell脚本中,注释以#字符开始,并且一直延伸到行的末尾。注释用于解释代码的目的、提供额外的说明和指导。注释不会被解释器执行,因此它们对脚本的运行没有影响。

1
2
# 这是一个注释,用于解释下面的命令的目的
echo "Hello, World!" # 这是另一个注释,用于解释echo命令的作用
2.6.2 文档

文档通常在脚本的顶部提供,用于描述脚本的用途、作者、版本等信息。文档通常包含在多行注释块中,并且使用特殊的标记(如 @#)来表示不同类型的信息。

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

###########################################################
# 脚本名称: example.sh
# 作者: John Doe
# 版本: 1.0
# 日期: 2024-02-18
# 描述: 这个脚本用于示例目的。它演示了如何在Shell中编写脚本。
###########################################################

# 主要代码开始
echo "Hello, World!"

文档部分可以包含脚本的名称、作者、版本号、日期、描述等信息。这些信息对于其他人阅读和理解脚本非常有帮助。可以根据需求扩展文档部分,例如添加参数说明、示例用法等。

通过使用注释和文档,您可以使Shell脚本更易于阅读、理解和维护。这有助于促进代码的可维护性和可重用性。

3. 控制流结构

3.1 条件语句

在Shell脚本中,条件语句允许您根据条件的真假来执行不同的代码块。

bash条件测试

3.1.1 if 语句:

if 语句用于在条件为真时执行代码块。它的基本结构如下:

1
2
3
4
5
6
7
if [ condition ]; then
# 条件为真时执行的代码块
elif [ condition2 ]; then
# 条件2为真时执行的代码块
else
# 所有条件都不满足时执行的代码块
fi

其中,condition 是一个表达式,它可以是命令的退出状态、比较操作或其他测试。条件可以包含逻辑运算符(如 &&||)和括号来组合多个条件。

1
2
3
4
5
6
# 示例:检查文件是否存在
if [ -f "$file" ]; then
echo "$file 存在。"
else
echo "$file 不存在。"
fi
3.1.2 case 语句:

case 语句用于测试一个变量与多个模式之间的匹配情况。它的基本结构如下:

1
2
3
4
5
6
7
8
9
10
11
case expression in
pattern1)
# 匹配 pattern1 时执行的代码块
;;
pattern2)
# 匹配 pattern2 时执行的代码块
;;
*)
# 如果没有匹配的模式时执行的代码块
;;
esac

expression 是要测试的变量,pattern 是匹配模式。如果expression匹配了某个模式,那么与该模式相关联的代码块将被执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 示例:根据用户输入执行不同的操作
case $input in
start)
echo "启动服务..."
;;
stop)
echo "停止服务..."
;;
restart)
echo "重启服务..."
;;
*)
echo "未知操作:$input"
;;
esac
3.1.3 test 命令:

test 命令用于执行各种条件测试,并返回相应的退出状态。它经常与 if 语句结合使用,以便进行文件测试、字符串比较、数值比较等。

1
2
3
4
5
6
# 示例:检查变量是否为空
if test -z "$variable"; then
echo "变量为空。"
else
echo "变量不为空。"
fi

这些条件语句可以帮助您根据不同的条件执行不同的代码,从而实现更灵活的脚本逻辑。

在 Bash 中,[ ][[ ]] 都是条件测试命令。它们之间有一些区别:

  1. 语法:

    • [ ] 是传统的条件测试命令,它使用单词形式,并且需要在每个条件测试之间使用空格来分隔。
    • [[ ]] 是 Bash 的扩展条件语法,它使用双方括号,并且更加灵活,不需要像 [ ] 那样严格使用空格来分隔条件。
  2. 功能:

    • [ ] 提供基本的条件测试功能,例如测试变量是否为空、字符串比较、文件属性等。
    • [[ ]] 提供了更多的功能和灵活性,例如支持模式匹配、正则表达式、逻辑运算符等,并且更加安全,不会出现某些意外行为。
  3. 性能:

    • [[ ]] 通常比 [ ] 更快,因为它是 Bash 的内置命令,而不是外部命令。此外,[[ ]] 的语法更简洁,不需要像 [ ] 那样频繁地调用外部命令。

综上所述,[[ ]] 是 Bash 推荐的条件测试语法,它提供了更多的功能和更好的性能,并且更加安全。在编写 Bash 脚本时,建议尽可能使用 [[ ]] 来进行条件测试。

3.2 循环结构

在Shell脚本中,循环结构允许您多次执行一段代码,直到满足某个条件。Shell脚本支持 forwhileuntil 循环。

3.2.1 for 循环:

for 循环用于迭代一个列表中的元素,并对每个元素执行一段代码。其基本结构如下:

1
2
3
for variable in list; do
# 在列表中迭代的代码块
done

其中,variable 是用于保存当前迭代元素的变量,list 是一个包含多个元素的列表。在每次迭代中,variable 将依次取列表中的每个元素的值。

1
2
3
4
5
# 示例:遍历数组中的元素
fruits=("apple" "banana" "orange")
for fruit in "${fruits[@]}"; do
echo "我喜欢吃 $fruit。"
done
3.2.2 while 循环:

while 循环用于在条件为真时重复执行一段代码。其基本结构如下:

1
2
3
while condition; do
# 条件为真时重复执行的代码块
done

在每次迭代中,condition 条件表达式被评估,如果为真,则执行循环体中的代码块。

1
2
3
4
5
6
7
8
# 示例:计算1到10的和
sum=0
counter=1
while [ $counter -le 10 ]; do
sum=$((sum + counter))
counter=$((counter + 1))
done
echo "1 到 10 的和为: $sum"
3.2.3 until 循环:

until 循环与 while 循环相反,它在条件为假时执行一段代码。其基本结构如下:

1
2
3
until condition; do
# 条件为假时重复执行的代码块
done

while 循环不同,until 循环在条件为假时执行循环体中的代码块,直到条件为真。

1
2
3
4
5
# 示例:生成随机数直到大于等于 90
until [ $number -ge 90 ]; do
number=$((RANDOM % 100))
echo "生成的随机数为:$number"
done

这些循环结构允许您编写更灵活的脚本,以便根据条件重复执行代码块或遍历列表中的元素。

3.2.4 select 循环

select 是 Bash shell 中的一个内置命令,用于创建交互式菜单。它可以帮助用户从一组预定义的选项中进行选择,并根据用户的选择执行相应的操作。

select 命令的基本语法如下:

1
2
3
4
5
select variable in option1 option2 option3 ...
do
# 命令列表
# 根据用户选择执行相应的操作
done

在这个语法中:

  • variable 是一个变量名,用于存储用户选择的选项。
  • option1 option2 option3 ... 是一系列可选的选项,它们会显示给用户。
  • dodone 之间是一个代码块,其中包含根据用户选择执行的操作。

select 命令会显示一个菜单,列出由 in 后面的选项定义的选项,并等待用户输入选择。一旦用户选择了一个选项,脚本会将选项的编号或者内容存储到指定的变量中,并执行 dodone 之间的代码块。

以下是一个简单的示例,演示了如何使用 select 命令创建一个选择菜单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/bin/bash

PS3="请选择要执行的操作: "
options=("备份数据" "恢复数据" "查看日志" "退出")

select opt in "${options[@]}"
do
case $opt in
"备份数据")
echo "执行备份操作..."
;;
"恢复数据")
echo "执行恢复操作..."
;;
"查看日志")
echo "执行查看日志操作..."
;;
"退出")
echo "退出菜单"
break
;;
*)
echo "无效选项,请重新选择"
;;
esac
done

4. 输入输出

在Shell脚本中,输入输出是非常重要的,它允许您与用户交互并与其他程序进行通信。Shell脚本通常使用标准输入(stdin)、标准输出(stdout)和标准错误(stderr)来处理输入和输出。

4.1 标准输入(stdin)

标准输入通常来自键盘或另一个命令的输出。您可以使用 read 命令从标准输入中读取用户输入。

1
2
3
echo "请输入您的姓名:"
read name
echo "您好,$name!"

在这个例子中,read 命令从标准输入中读取用户输入,并将其赋值给变量 name,然后使用该变量打印出一条问候消息。

4.2 标准输出(stdout)

标准输出通常是您的脚本产生的输出,它默认会显示在终端上。您可以使用 echo 命令来向标准输出中写入内容。

1
echo "这是一条消息。"

4.3 标准错误(stderr)

标准错误通常用于输出错误消息和警告。与标准输出类似,标准错误默认也会显示在终端上,但是您可以将其重定向到文件中。

1
cat non_existent_file 2> error.log

在这个例子中,cat 命令尝试打开一个不存在的文件,错误消息将被重定向到名为 error.log 的文件中。

4.4 文件重定向

除了标准输入、输出和错误之外,您还可以使用文件重定向来控制输入和输出。以下是一些常见的文件重定向操作符:

  • >:将输出重定向到文件中,如果文件不存在则创建,如果存在则覆盖。
  • >>:将输出追加到文件中,如果文件不存在则创建。
  • <:将文件内容重定向到命令的输入中。
  • |:管道操作符,将一个命令的输出作为另一个命令的输入。
1
2
echo "这是一条消息。" > output.txt
cat input.txt | grep "keyword"

在这个例子中,第一条命令将字符串 “这是一条消息。” 写入到 output.txt 文件中,第二条命令将 input.txt 文件的内容发送到 grep 命令,并在其中搜索关键字 “keyword”。

这些是Shell脚本中处理输入输出的基本方法。您可以使用这些方法与用户交互,并与其他程序进行通信。

  • 除了使用 read 命令从标准输入中读取用户输入外,您还可以使用重定向符号 < 将文件内容重定向到命令的输入中。
1
2
3
while read line; do
echo "读取的行:$line"
done < input.txt

这个例子中,while 循环会从 input.txt 文件中读取每一行,并将其赋值给 line 变量。

4.5 Here Doc 和Here String

在 Shell 脚本中,Here Document(Here Doc)和 Here String 是用于处理输入的两种特殊技术。

1. Here Document(Here Doc)

Here Document 是一种将多行文本作为输入传递给命令或脚本的方法。它使用 << 操作符,后面跟着一个标记(可以是任何标识符),然后是要输入的文本,直到遇到标记结束。

1
2
3
4
cat << EOF
这是一段多行文本。
它可以包含任意内容,直到遇到 EOF 标记。
EOF

在这个示例中,cat 命令会将 Here Doc 中的文本作为输入,并打印出来。EOF 是结束标记,用于指示 Here Doc 的结束。

2. Here String

Here String 是一种将字符串作为输入传递给命令或脚本的方法。它使用 <<< 操作符,后面跟着要输入的字符串。

1
grep "pattern" <<< "这是一段输入字符串"

在这个示例中,<<< 操作符将字符串 “这是一段输入字符串” 作为输入传递给 grep 命令,并在其中搜索指定的模式。

区别:

  • Here Document 用于传递多行文本作为输入,而 Here String 用于传递单行字符串作为输入。
  • Here Document 使用 << 操作符,而 Here String 使用 <<< 操作符。

这些特殊技术使得 Shell 脚本能够更灵活地处理输入,无论是从文件、变量还是直接输入的文本。

4.6 文件描述符

文件描述符(File Descriptor)是操作系统中用于标识和访问文件或其他I/O资源的整数。在Unix和类Unix系统中,一般情况下有三个标准的文件描述符:

  1. 标准输入(stdin): 文件描述符为0。它通常用来表示程序的输入来源,例如键盘输入或者另一个命令的输出。

  2. 标准输出(stdout): 文件描述符为1。它通常用来表示程序的输出目标,例如终端输出或者重定向到文件中。

  3. 标准错误(stderr): 文件描述符为2。它通常用来表示程序的错误消息输出目标,例如终端输出或者重定向到文件中。

这些标准文件描述符在Shell脚本中非常有用,因为它们允许您控制输入和输出的来源和目标。除了标准文件描述符外,您还可以创建自定义文件描述符来引用其他文件或管道。

创建自定义文件描述符:
在Shell脚本中,您可以使用 exec 命令来创建自定义文件描述符,并将其与文件或管道关联起来。

1
2
3
4
5
6
# 将文件描述符3关联到文件test.txt
exec 3< test.txt
# 从文件描述符3读取一行
read line <&3
# 关闭文件描述符3
exec 3<&-

在这个示例中,exec 3< test.txt 将文件描述符3关联到文件 test.txt,然后可以使用 <&3 从文件描述符3读取数据。最后,使用 exec 3<&- 关闭文件描述符3。

自定义文件描述符可以用于各种用途,如输入重定向、输出重定向、管道等。它们为Shell脚本提供了更灵活的输入输出控制机制。

使用文件描述符重定向命令的输出目标,将输出内容发送到文件或另一个命令中。

1
2
3
4
5
6
7
# 将文件描述符4关联到文件output.txt,并将命令的标准输出重定向到文件描述符4
exec 4> output.txt
# 将消息写入文件描述符4,即写入到output.txt文件中
echo "这是一条消息。" >&4
# 关闭文件描述符4
exec 4>&-

使用文件描述符来创建管道,将一个命令的输出作为另一个命令的输入。

1
2
3
4
5
6
7
8
9
10
11
12
# 创建管道,将命令1的输出重定向到文件描述符3,然后从文件描述符3读取数据作为命令2的输入
exec 3< <(command1)
# 从文件描述符3读取数据,即读取command1的输出
read data <&3
# 关闭文件描述符3
exec 3<&-

# 或者更简洁的方式
command1 |& command2

# |& 用于将一个命令的标准输出和标准错误输出一起重定向到另一个命令的标准输入。它处理命令的所有输出,包括标准输出和标准错误输出。
# | 只处理标准输出,而 |& 处理标准输出和标准错误输出。

在Shell脚本中,您可以使用 ulimit -a 命令来显示当前Shell会话的各种限制,包括文件描述符数量。文件描述符的数量由 open filesmax user processes 参数表示。

1
ulimit -a | grep "open files"

这将显示当前Shell会话中允许打开的最大文件描述符数量。请注意,此处显示的值可能受操作系统和用户权限限制的影响。

4.7 命令替换

命令替换是一种Shell脚本中常用的技术,它允许将命令的输出结果作为字符串嵌入到另一个命令中,从而实现动态构建命令的目的。在Shell中,有两种形式的命令替换:使用反引号(`command`)或使用 $()

  • 使用反引号进行命令替换:
    反引号(`)是一种用于命令替换的特殊字符,可以将其内部的命令执行,并将其输出作为字符串返回。
1
result=`command`
  • 使用 $() 进行命令替换:
    $() 是另一种常用的命令替换语法,与反引号的作用相同,但通常更易读、更易于嵌套使用。
1
result=$(command)
  • 使用示例:
    示例1:将命令的输出赋值给变量:
1
2
files_count=$(ls | wc -l)
echo "当前目录下有 $files_count 个文件。"

示例2:在命令中使用命令替换:

1
echo "当前用户是 $(whoami)。"

示例3:嵌套使用命令替换:

1
echo "当前时间是 $(date +%Y-%m-%d_%H:%M:%S)。"
1
2
result=$(ls -l $(dirname $(which bash)))
# 这个命令首先使用 which bash 命令找到 bash 的安装路径,然后使用 dirname 命令获取其所在目录,最后将该目录作为参数传递给 ls -l 命令,以显示该目录的详细信息。
1
2
3
# 将命令替换的结果直接作为命令的参数传递给另一个命令。
grep "$(date +%Y-%m-%d)" logfile.txt
# 这个命令使用 date +%Y-%m-%d 获取当前日期,并将其作为参数传递给 grep 命令,以搜索包含当前日期的日志文件。
1
2
3
4
5
6
7
8
9
10
# 利用Shell的控制结构和条件语句,动态地构建命令替换,以根据不同的条件执行不同的命令。
if [ "$user" = "root" ]; then
command=$(echo "service apache2 restart")
else
command=$(echo "sudo service apache2 restart")
fi

eval "$command"

# 在这个例子中,根据当前用户是 root 还是其他用户,构建不同的命令,并通过 eval 执行。请注意,eval 是一个强大但危险的命令,因为它会执行传递给它的任何命令字符串。
1
2
3
4
5
6
7
# 这个例子中,循环遍历所有的 .txt 文件,并构建对每个文件执行的命令,然后使用 eval 执行每个命令。
for file in $(ls *.txt); do
echo "Processing file: $file"
process_command=$(echo "process_file $file")
eval "$process_command"
done

1
2
3
4
5
6
7
8
9
10
11
# 在这个示例中,我们定义了一个名为 servers 的数组,其中包含了多个服务器的名称。然后,我们使用循环遍历数组中的每个服务器,通过SSH连接到服务器,并获取其运行时间。

servers=("server1" "server2" "server3")

for server in "${servers[@]}"; do
echo "连接到服务器: $server"
ssh_command=$(echo "ssh $server uptime")
uptime=$(eval "$ssh_command")
echo "服务器 $server 的运行时间: $uptime"
done

通过结合其他Shell特性,如条件语句、循环、函数和数组等,我们可以将命令替换的结果与其他功能结合起来,从而实现更加灵活和强大的功能。这样可以让我们编写更复杂、更可靠的Shell脚本来满足各种需求。

命令替换的优点:

  • 方便实用: 命令替换使得在Shell脚本中处理命令输出变得非常方便,可以直接将命令输出用于后续操作。
  • 可读性高: 使用 $() 语法进行命令替换通常比使用反引号更加直观和易读。

注意事项:

  • 引号处理: 在命令替换中,通常建议将命令替换语法放在双引号中,以避免由于特殊字符或空白字符而导致的意外行为。
  • 嵌套使用: 可以嵌套使用命令替换来构建复杂的命令,但需要注意维护代码的可读性和可维护性。

命令替换是Shell脚本中非常常用且强大的技术,可以帮助您更轻松地处理命令的输出结果,并构建动态的命令。

4.8 进程替换

进程替换(Process Substitution)是一种特殊的Shell操作,允许将命令的输入或输出重定向到临时文件或命名管道中,以便在命令之间传递数据,而无需创建显式的临时文件或管道。它提供了一种简洁而方便的方式来处理需要文件输入或输出的命令。

4.8.1 进程替换的语法:

进程替换使用了尖括号(<())或括号(>())来表示,后面跟着要执行的命令。

  • <():将命令的输出重定向到临时文件中,并返回该临时文件的路径。
  • >():将命令的输入重定向到一个命名管道中,并返回该管道的文件描述符。

使用示例:

  • 将命令的输出重定向到临时文件中:
1
sort <(command1)

这个命令将 command1 命令的输出重定向到一个临时文件中,然后 sort 命令将该临时文件的内容作为输入进行排序。

1
2
3
4
# 将命令替换的结果作为文件名参数传递给命令:
diff <(sort file1) <(sort file2)

# 在这个示例中,我们使用了两次进程替换,将 file1 和 file2 的内容都排序后,将结果传递给 diff 命令进行比较。这样可以避免手动创建临时文件。
1
2
3
4
5
6
7
8
9
# 将进程替换的结果作为参数传递给函数:

process_data() {
# 处理数据
}

process_data <(generate_data)

# 在这个示例中,我们定义了一个名为 process_data 的函数,它接受一个数据文件作为参数。然后,我们使用进程替换调用 generate_data 函数并将其输出结果作为 process_data 函数的参数传递。
1
2
3
4
for file in *.txt; do
echo "文件 $file 的行数: $(wc -l < <(cat "$file"))"
done

  • 将命令的输入重定向到命名管道中:
1
command1 >(command2)

这个命令将 command1 命令的输出重定向到一个命名管道中,然后 command2 命令将该管道作为输入进行处理。

4.8.2 进程替换的优点:
  • 避免了创建显式的临时文件或管道: 进程替换使得处理需要文件输入或输出的命令变得更加简单和方便,无需手动创建临时文件或管道。
  • 效率高: 进程替换利用了操作系统的文件缓存和磁盘读取优化,因此在处理大量数据时可能比手动创建临时文件或管道更高效。
4.8.3 注意事项:
  • 临时文件的生命周期: 进程替换创建的临时文件或管道的生命周期与命令执行的时间一样长,命令执行完毕后会自动删除临时文件或管道。
  • 不是所有的命令都支持: 并非所有的Shell命令都支持进程替换,某些复杂的命令可能会出现问题或不支持。

进程替换是Shell脚本中一种非常方便的技术,特别适用于需要处理文件输入输出的情况。

5. Shell 函数

当你在编写 Bash 脚本或者在交互式的 Bash Shell 中使用函数时,你实际上是在定义一段可重用的代码块。Shell 函数允许你将一系列命令组合在一起,使得它们可以被多次调用,从而提高了脚本的可读性、可维护性和复用性。

5.1 函数的定义和调用

5.1.1 定义函数

在 Bash 中,你可以使用 function 关键字或者直接使用函数名来定义函数。以下是定义函数的通用语法:

1
2
3
4
5
function_name() {
# 函数体
# 这里是一系列的命令
}

或者:

1
2
3
4
5
function function_name {
# 函数体
# 这里是一系列的命令
}

函数名可以包含字母、数字和下划线,但不能以数字开头。

值得注意的是,函数定义通常放在脚本的开头部分,以便在整个脚本中都能够使用它们。

5.1.2 调用函数

一旦定义了函数,你可以通过函数名来调用它。调用函数时,可以像调用其他命令一样直接使用函数名:

1
function_name

5.2 函数参数

在 Bash 脚本中,函数可以接受参数,这使得函数更加灵活和通用。函数参数是在调用函数时传递给函数的值,函数可以使用这些值来执行特定的操作。

5.2.1 函数传参

在调用函数时,可以在函数名后面列出参数。参数用空格分隔。

1
function_name parameter1 parameter2 parameter3
5.2.2 函数内部访问参数

在函数内部,可以使用 $1, $2, $3, … 的形式来引用传递给函数的参数。例如,$1 引用第一个参数,$2 引用第二个参数,依此类推。

1
2
3
countfiles() {
ls -l "$1" | grep "^-" | wc -l
}
5.2.3 特殊参数
  • $0: 当前脚本的名称。
  • $#: 传递给函数的参数个数。
  • $@: 传递给函数的所有参数列表。
  • $*: 传递给函数的所有参数,作为单个字符串
1
2
3
4
5
6
7
8
print_all_parameters() {
for param in "$@"; do
echo "$param"
done
}

print_all_parameters "apple" "banana" "orange"

5.2.4 参数默认值

设置参数的默认值可以使函数更加灵活,因为它允许函数在不同的情况下具有不同的行为。如果没有默认值,函数可能会对缺少参数的情况做出假设,这可能导致不可预测的结果或错误。

对于具有多个可选参数的函数,设置默认值可以降低调用函数时的复杂性。用户只需提供他们感兴趣的参数,而不必为每个参数都提供值。这样可以简化函数的使用,并使代码更易于理解和维护。

在 Bash 中,为函数参数设置默认值有几种常用的方式:

  • 使用变量赋值
1
2
3
4
5
6
7
greet() {
name=${1:-"world"}
echo "Hello, $name!"
}

greet

在这个例子中,${1:-"world"} 表示如果第一个参数 $1 未被传递或为空,则将 name 变量设置为 "world",否则将其设置为传递的参数值。

  • 使用条件语句
1
2
3
4
5
6
7
8
9
10
11
greet() {
if [ -z "$1" ]; then
name="world"
else
name="$1"
fi
echo "Hello, $name!"
}

greet

这里使用 -z 来检查参数是否为空,如果为空,则将 name 设置为 "world",否则将其设置为传递的参数值

  • 直接使用参数变量
1
2
3
4
5
6
7
8
9
10
greet() {
name="${1:-}"
if [ -z "$name" ]; then
name="world"
fi
echo "Hello, $name!"
}

greet

在这个例子中,直接使用 ${1:-} 获取第一个参数,如果参数为空,则将 name 设置为 "world"

5.3 局部和全局变量

在 Bash 中,在函数内部不使用 local 关键字定义的变量默认情况下是全局变量。这与其他编程语言的行为有所不同,在其他语言中,未经声明的变量通常是局部变量。

使用局部变量可以避免在函数内部意外地修改全局变量的值,从而提高代码的健壮性。

1
2
3
4
5
6
7
8
9
10
11
12
13
greet() {
> local name=beizi
> age=18
> echo "hello $name ,your age is $age"
> }
➜ greet
hello beizi ,your age is 18

# 这里输出空白行,因为 name 是局部变量,对于函数外部是不可见的
echo $name

echo $age
18

在函数内部,如果需要使用全局变量,可以直接访问。任何在函数外部定义的变量都是全局变量,它们对于整个脚本都是可见的。

第二部分:进阶

6. 文件操作

6.1 文件测试

在Shell编程中,文件测试用于检查文件的各种属性,例如文件是否存在、是否是目录、是否可读可写等。在Bash中,可以使用条件语句结合一系列的文件测试来判断文件的属性。

  1. -e:文件是否存在。
  2. -f:文件是否为普通文件。
  3. -d:文件是否为目录。
  4. -r:文件是否可读。
  5. -w:文件是否可写。
  6. -x:文件是否可执行。
  7. -s:文件是否非空。
  8. -z:文件是否为空。
  9. -h-L:文件是否为符号链接。
  10. -N:文件自上次读取以来是否已被修改。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if [ -e "/etc/nginx/nginx.conf" ]; then
echo "Nginx配置文件存在"
else
echo "Nginx配置文件不存在"
fi

# 在管理日志文件时,经常需要检查日志文件是否有新的日志记录:
if [ -s "/var/log/nginx/access.log" ]; then
echo "Nginx访问日志文件非空"
else
echo "Nginx访问日志文件为空"
fi

# 在执行备份任务前,需要确保备份文件存在且是最新的:
if [ -e "/backup/database_backup.sql" ] && [ -N "/backup/database_backup.sql" ]; then
echo "数据库备份文件存在且是最新的"
else
echo "数据库备份文件不存在或不是最新的"
fi

# 在配置安全相关的服务时,需要确保证书文件存在且是有效的:
if [ -e "/etc/ssl/certs/server.crt" ] && openssl x509 -checkend 86400 -noout -in "/etc/ssl/certs/server.crt"; then
echo "SSL证书存在且有效"
else
echo "SSL证书不存在或已过期"
fi

# 在执行自动化任务时,需要确保脚本文件存在且具有执行权限:
if [ -x "/scripts/backup.sh" ]; then
echo "备份脚本存在并执行"
/scripts/backup.sh
else
echo "备份脚本不存在或不可执行"
fi

当然还有一些其他测试

  1. 检查文件是否是空白文件

    • -s:文件是否有大小(即非空文件)。
    • -z:文件是否为空(即文件大小为零)。
  2. 检查文件的设备号和节点号

    • -h:文件是否是一个符号链接。
    • -r:文件是否可读。
    • -w:文件是否可写。
    • -x:文件是否可执行。
  3. 检查文件的权限

    • -u:文件是否设置了 SUID 位(Set User ID)。
    • -g:文件是否设置了 SGID 位(Set Group ID)。
    • -k:文件是否设置了粘滞位(Sticky Bit)。
  4. 检查文件的类型

    • -c:文件是否为字符设备。
    • -b:文件是否为块设备。
    • -p:文件是否为命名管道(FIFO)。
    • -S:文件是否为套接字文件。
  5. 检查文件的最后访问时间和修改时间

    • -nt:文件是否比另一个文件新。
    • -ot:文件是否比另一个文件旧。

这些文件测试方法可以在脚本编写中进行更全面的文件属性检查。

6.2 文件操作命令(cpmvrm 等)

文件操作命令用于在Unix/Linux系统中对文件进行各种操作,包括创建、复制、移动、删除、重命名、查看内容等。以下是一些常见的文件操作命令:

6.2.1 创建文件
  • touch filename:创建空文件或更新文件的访问和修改时间戳。如果文件已存在,则它的时间戳会被更新。
  • echo "content" > filename:将文本内容写入文件。
  • cat > filename: 使用cat命令可以创建一个空文件,或者向现有文件中追加内容。输入文本后按 Ctrl + D 结束输入。
  • printf "content" > filename类似于echo,但更灵活。它可以用来向文件中写入格式化的文本。
6.2.2 复制文件

cp 是一个常用的 Linux/Unix 命令,用于复制文件或目录。以下是 cp 命令的详解:

1
cp [选项] 源文件 目标文件或目录

常用选项

  • -i:在覆盖目标文件之前提示用户确认。
  • -r-R:递归地复制目录及其内容。
  • -p:保留文件属性,包括权限、所有者、时间戳等。
  • -a:递归地复制目录,并保留所有文件属性,等同于 -dpR
  • -u:仅在源文件较新时才复制文件。
  • -v:显示复制的详细信息。
  • -n:如果目标文件已存在,则不覆盖。
  • -l:创建硬链接而不是复制文件。
  • -s:创建符号链接而不是复制文件。
  • -d:将保留符号链接的属性
  • --backup[=CONTROL]:在覆盖目标文件之前进行备份
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 将文件复制到目录
cp file1.txt /path/to/directory/

# 递归地复制目录及其内容
cp -r directory1/ directory2/

# 保留文件属性
cp -p file1.txt file2.txt

# 递归地复制目录,并保留所有文件属性
cp -a directory1/ directory2/

# 使用备份选项
cp --backup=numbered file1.txt file2.txt
1
2
3
4
lrwxr-xr-x  1 user  group      15 Feb 21 10:00 source_file -> /path/to/actual_file
cp -d source_file /path/to/destination
# 在/path/to/destination目录中查看,将会看到一个名为source_file的符号链接,指向与原始文件相同的目标文件。
lrwxr-xr-x 1 user group 15 Feb 21 10:10 source_file -> /path/to/actual_file

-d选项保留了符号链接的属性,创建了一个指向同样目标的新的符号链接。

如果省略了-d选项,cp命令将会复制符号链接所指向的实际文件或目录,而不是保留符号链接本身。

6.2.3 移动/重命名文件

mv用于移动文件或重命名文件或目录

1
mv [选项] 源文件/目录 目标文件/目录

主要选项

  • -i:在覆盖前提示确认。
  • -f:强制执行移动操作,不进行提示。
  • -v:显示详细的移动过程
1
2
3
# 将多个文件移动到目录
mv file1 file2 file3 /path/to/destination/directory

6.2.4 删除文件

rm用于删除文件或目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 基本用法
rm 文件名
# 删除多个文件
rm 文件1 文件2 文件3 ...

# rm 不会删除目录。要删除一个目录及其内容,需要使用 -r 或 --recursive 选项:
rm -r 目录名

# 要在每次删除之前进行确认提示,可以使用 -i 或 --interactive 选项:
rm -i 文件名

# 要强制删除文件而无需任何确认提示,可以使用 -f 或 --force 选项:
rm -f 文件名

# 您可以使用 -v 或 --verbose 选项使 rm 在删除每个文件时打印一条消息:
rm -v 文件名

# 可使用通配符,
rm *.txt

# 默认情况下,如果尝试删除一个不存在的文件,rm 会显示错误消息。您可以使用 --ignore-non-existent 选项来忽略这些错误消息:
rm --ignore-non-existent 不存在的文件名

# 安全删除:
某些版本的 rm 提供了 -P 或 --overwrite 等选项,以覆盖已删除的文件,以防止其内容被恢复。这对于保护敏感数据非常有用。

注意

  • 没有回收站: 与通过图形界面删除文件不同,rm 会永久删除文件,而不是将它们移动到回收站。请谨慎使用,因为删除的文件通常无法轻易恢复。
  • 不可逆转: 一旦使用 rm 删除文件,通常是无法恢复的。在使用 -r-f 等选项时,请仔细检查您的命令,以避免意外数据丢失。
6.2.5 查看文件内容
1. cat

cat 是一个常用的命令行工具,用于显示文件内容,它的名称是 “concatenate” 的缩写。尽管它的主要功能是显示文件内容,但它还可以用于将多个文件连接在一起并输出到标准输出流。

常见选项:

-n, –number:显示输出的行号。

-b, –number-nonblank:仅对非空行编号。

-s, –squeeze-blank:压缩连续的空行为一行。

-E, –show-ends:在每行末尾显示 $ 符号。

-T, –show-tabs:将制表符显示为 ^I

-v, –show-nonprinting:显示非打印字符,以八进制转义序列形式。

-A, –show-all:等效于 -vET 的组合,显示所有的非打印字符,包括换行符和制表符。

基本用法:

1
cat [OPTION]... [FILE]...
  • 如果不提供任何文件参数,则 cat 会从标准输入中读取数据并将其输出到标准输出。
  • 如果提供一个或多个文件参数,则 cat 会按照它们在命令行中出现的顺序依次读取它们的内容,并将它们连接起来输出到标准输出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 显示文件内容:
cat filename

# 2. 将多个文件内容连接起来输出:
cat file1 file2

# 3. 将文件内容添加到另一个文件末尾:
cat file1 >> file2

# 4. 显示文件内容并显示行号:
cat -n filename

# 5. 显示文件内容并显示非打印字符:
cat -v filename

cat 是一个简单但功能强大的命令行工具,用于处理文本文件。它在处理小型文件或快速查看文件内容时非常方便,但在处理大型文件时可能会有性能问题。

2. more或less
  • more filenameless filename:逐页查看文件内容。

more 是一个命令行工具,通常用于查看文本文件的内容。它会按页显示文件内容,并等待用户按键来逐页浏览。more 命令的基本语法是:

1
more [选项] 文件名

其中,选项可以是:

-n:指定每页显示的行数。例如,more -10 filename 将每页显示 10 行内容。

-d:显示每页内容之前先清除屏幕,然后显示页头信息。

-c:不进行逐页显示,直接将整个文件内容输出到屏幕。

-s:静默模式,禁止在底部显示文件名和提示符。

一些常用的 more 命令示例:

1
2
3
4
5
6
7
8
9
10
11
`more filename`:显示文件内容,按空格键显示下一页,按 Enter 键显示下一行,按 `q` 键退出。

`more -10 filename`:每页显示 10 行文件内容。

`more -d filename`:显示内容之前先清除屏幕,然后显示页头信息。

`more -c filename`:将整个文件内容直接输出到屏幕。

`more -s filename`:静默模式,不显示文件名和提示符。

# 这些选项可以根据实际需要进行组合使用。

less

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
除了基本用法外,`less` 还提供了一些高级功能和快捷键,使得在浏览文本文件时更加灵活和高效。以下是一些 `less` 的详细用法:

### 搜索:

- `/pattern`:在文件中向前搜索指定模式。
- `?pattern`:在文件中向后搜索指定模式。
- `n`:跳转到下一个匹配项。
- `N`:跳转到上一个匹配项。

### 导航:

- `g`:跳转到文件的第一行。
- `G`:跳转到文件的最后一行。
- `1g` 或 `gg`:跳转到文件的第一行。
- `nG` 或 `NG`:跳转到文件的第 n 行。
- `:n`:跳转到文件的第 n 行。

### 分页:

- `空格键`:向下滚动一页。
- `b`:向上滚动一页。

### 显示信息:

- `=`:显示文件信息,包括文件名、行数等。
- `:`:显示当前行号。

### 编辑文本:

- `v`:使用默认文本编辑器打开文件进行编辑。
- `e`:在当前行处打开默认文本编辑器进行编辑。
- `E`:在文件末尾打开默认文本编辑器进行编辑。

### 设置选项:

- `-N`:显示行号。
- `-I`:禁止搜索功能,更快地打开大型文件。
- `-S`:折叠长行,以适应终端窗口。

### 其他:

- `h`:显示帮助文档,列出所有可用的 `less` 命令。
- `q`:退出 `less`。
- `Ctrl + C`:退出 `less`。

通过掌握这些 `less` 的详细用法,你可以更灵活地浏览和管理文本文件,提高你在终端环境下处理文本的效率。
3.head
  • head filename:查看文件的前几行。

head 是一个常用的命令行工具,用于显示文本文件的开头部分。以下是关于 head 命令的详解:

基本用法:

1
head [OPTION]... [FILE]...
  • 如果不指定文件名,则 head 命令会从标准输入中读取数据,并显示开头部分。
  • 如果指定了一个或多个文件名,则 head 命令会显示每个文件的开头部分。

常用选项:

  1. -n, –lines=[-]NUM:指定要显示的行数。默认情况下,head 显示文件的前 10 行。可以使用 -n 选项来指定要显示的行数,例如 -n 20 将显示前 20 行。

  2. -c, –bytes=[-]NUM:指定要显示的字节数。可以使用 -c 选项来指定要显示的字节数,例如 -c 100 将显示文件的前 100 个字节。

  3. -q, –quiet, –silent:不显示文件名头部信息。当处理多个文件时,可以使用此选项来取消显示每个文件名的头部信息。

  4. -v, –verbose:总是显示文件名的头部信息。与 -q 选项相反,此选项用于强制显示每个文件名的头部信息。

  5. -z, –zero-terminated:使用 NUL 作为行分隔符。当处理二进制文件时,可以使用此选项来指定 NUL 字符(ASCII 值为 0)作为行分隔符。

  6. –help:显示帮助信息并退出。

  7. –version:显示版本信息并退出。

    • tail filename:查看文件的最后几行。
6.2.6 编辑文件
  • nano filenamevi filename:使用文本编辑器编辑文件。
vim

Vim 是一个强大的文本编辑器,它在命令行中运行,并提供了许多快捷键和命令来快速编辑文本文件。以下是一些基础的 Vim 使用方法:

  1. 进入 Vim: 在命令行中输入 vim,然后按 Enter 键。

  2. 基本移动:

    • 使用箭头键可以进行上下左右移动。
    • hjkl 分别代表左、下、上、右移动。
  3. 模式:

    • 正常模式 (Normal Mode): 这是默认模式,用于移动光标和执行命令。
    • 插入模式 (Insert Mode): 在此模式下,您可以输入文本。按下 i 键进入插入模式。
    • 可视模式 (Visual Mode): 用于选择文本。按下 v 键进入可视模式。
  4. 保存和退出:

    • 在正常模式下,按下 :w 保存文件。
    • 按下 :q 退出 Vim。
    • 若要保存并退出,可以组合使用::wq
  5. 撤销和重做:

    • 在正常模式下,按下 u 撤销上一步操作。
    • 若要重做,按下 Ctrl + r
  6. 删除和复制:

    • 在正常模式下,x 删除光标所在的字符。
    • dd 删除整行。
    • yy 复制整行。
  7. 粘贴:

    • 将文本粘贴到光标处,按下 p

Vim 的进阶技巧:

  1. 搜索和替换:

    • 在正常模式下,按下 / 进入搜索模式,输入要搜索的内容,然后按 Enter。使用 nN 在搜索结果之间进行导航。
    • 替换命令使用 :%s/old/new/g,其中 old 是要替换的内容,new 是替换后的内容。:wq保存并退出。
  2. 分屏编辑:

    • 使用 :split:vsplit 在水平或垂直方向分割窗口。
    • 使用 Ctrl + w 然后是 hjkl 在窗口之间进行移动。

    在 Vim 中取消分屏可以通过以下方法之一完成:

    1. 关闭当前窗口:

      • 如果你只是想关闭当前窗口而不是取消分屏,可以在要关闭的窗口中执行 :q 命令。这将关闭当前窗口但不会退出 Vim。
      • 如果想关闭所有窗口并退出 Vim,可以在每个窗口中执行 :qall:qa 命令。
    2. 合并窗口:

      • 如果你想要将多个窗口合并成一个,可以使用 :only 命令。执行该命令将关闭除当前窗口外的所有其他窗口,从而实现取消分屏的效果。
    3. 手动调整窗口大小:

      • 如果想要调整窗口大小以达到取消分屏的效果,可以使用 Ctrl + w 前缀键结合 +-<> 来调整当前活动窗口的大小。例如,Ctrl + w + 将增加当前窗口的高度,Ctrl + w > 将增加当前窗口的宽度。

    使用上述方法之一可以很容易地取消 Vim 中的分屏。

  3. 标记和跳转:

  • 使用 m 命令标记位置,例如 ma 标记为位置 a
  • 使用 '` 跳转到标记的位置,例如 'a`a 跳转到位置 a
  1. 宏录制和回放:
  • 按下 q 开始录制宏,然后选择一个字母作为宏的名称,例如 qa
  • 在录制期间执行一系列命令,然后按下 q 停止录制宏。
  • 使用 @a 来执行刚刚录制的宏。
  1. 自动补全:
  • 在插入模式下,按下 Ctrl + nCtrl + p 进行自动补全。
  1. 自定义配置:
  • 编辑 ~/.vimrc 文件来自定义 Vim 的行为,比如设置缩进、主题等。
  1. 插件管理:
  • 使用插件管理器如 Vundle、Pathogen 或 Vim-Plug 来安装和管理插件,扩展 Vim 的功能。

这些是 Vim 的一些进阶技巧,能够使你更加高效地使用 Vim 来编辑文本。继续练习和探索,你会发现 Vim 的强大之处。

当然,请继续学习 Vim 的进阶技巧:

  1. 粘贴:

    • 在正常模式下:
      • 按下 p 将剪贴板中的内容粘贴到光标后。
      • 按下 P 将剪贴板中的内容粘贴到光标前。
    • 在插入模式下:
      • 退出插入模式(按下 Esc)后,按下 Ctrl + r 然后是 + 来粘贴剪贴板中的内容。
  2. 撤销和重做:

    • 撤销:
      • 在正常模式下,按下 u 来撤销上一步操作。
      • 可以多次按下 u 来连续撤销多个操作。
    • 重做:
      • 在正常模式下,按下 Ctrl + r 来重做被撤销的操作。
  3. 折叠:

    • 使用折叠功能可以将文本按照一定的规则折叠起来,使得文本结构更加清晰。
    • 操作命令:
      • zc 折叠当前光标所在的折叠。
      • zo 展开当前光标所在的折叠。
      • zC 递归折叠,即折叠当前及所有嵌套的折叠。
      • zO 递归展开,即展开当前及所有嵌套的折叠。
      • zr 打开所有折叠。
      • zm 关闭所有折叠。
  4. 多文件编辑:

    • 使用 :e filename 打开另一个文件进行编辑。
    • 使用 :ls 查看当前打开的文件列表。
    • 使用 :bnext:bprev 切换到下一个或上一个文件。
  5. 查看帮助:

    • 在 Vim 中,可以使用 :help 命令查看帮助文档,如 :help motion 将显示有关移动命令的帮助信息。
    • 若要关闭帮助窗口,请按下 Ctrl + w

多文件编辑:

  • 使用 :e filename 命令可以打开另一个文件进行编辑,这将在当前窗口打开新文件。
  • 使用 :sp filename:vsp filename 命令可以在水平或垂直方向分割窗口并打开另一个文件。
  • 使用 :bn:bp 命令在多个缓冲区之间切换文件。

让我们继续学习 Vim 的更多功能:

  1. 标签页操作:

    • 使用 :tabnew 命令可以打开一个新的标签页。
    • 使用 gt 切换到下一个标签页,使用 gT 切换到上一个标签页。
    • 使用 :tabclose 关闭当前标签页,使用 :tabcloseall 关闭所有标签页。
  2. 文本对象操作:

    • Vim 提供了许多文本对象,可以使编辑更加精确。
    • 例如,aw 表示一个单词,as 表示一个句子,ap 表示一个段落。
    • 你可以在普通模式下结合操作符和文本对象进行操作,比如 daw 删除一个单词,ci" 修改引号内的内容。
  3. 会话管理:

    • 使用 :mksession 命令可以保存当前会话状态。
    • 使用 vim -S Session.vim 命令可以恢复之前保存的会话。
  4. 窗口缩放:

    • 使用 Ctrl + w 后跟 >< 可以增加或减少当前窗口的宽度。
    • 使用 Ctrl + w 后跟 +- 可以增加或减少当前窗口的高度。
  5. 撤销树:

    • 使用 :GundoToggle 命令可以打开撤销树视图,它显示了所有的撤销历史记录,并允许你在历史记录中进行导航。
  6. 文件浏览器:

    • 使用 :Explore 命令可以打开文件浏览器,查看当前目录中的文件。
    • 使用 :Vexplore 可以在垂直分割的窗口中打开文件浏览器。

这些是一些更加高级和专业的 Vim 功能,能够让你更加高效和方便地进行文本编辑和管理。继续学习并不断练习,你会成为一个熟练的 Vim 用户。

当然,请继续学习以下关于 Vim 的功能和技巧:

  1. 多窗口跳转:
  • 使用 Ctrl + w 后跟 w 可以在不同窗口之间进行切换。
  • 使用 Ctrl + w 后跟 hjkl 可以在窗口之间进行快速移动。
  1. 标签页与窗口间跳转:
  • 使用 :tabnextgt 可以在不同标签页之间进行切换。
  • 使用 :tabpreviousgT 可以切换到前一个标签页。
  1. 寄存器操作:
  • 使用 :reg 命令可以查看当前所有寄存器的内容。
  • 使用 "ay 可以将选中的内容复制到寄存器 a 中,使用 "ap 可以将寄存器 a 的内容粘贴出来。
  1. 自动命令:
  • ~/.vimrc 中可以设置自动命令,以便在特定事件发生时自动执行某些操作。
  • 例如,可以使用 autocmd BufRead *.txt set tabstop=4 来设置在读取 .txt 文件时将 tab 键设为 4 个空格。
  1. 片段插入:
  • 使用插件如 UltiSnips 或 SnipMate 可以方便地插入代码片段,提高编码效率。
  • 定义好的代码片段可以通过触发关键词后自动展开为完整代码块。
  1. 列编辑模式:
  • 在正常模式下,按下 Ctrl + v 进入列编辑模式,可以对多行文本的某一列进行编辑。
  • 在列编辑模式下,可以使用方向键或 hjkl 键选择文本区域,然后进行编辑。
  1. 命令行模式下的搜索:
  • 在命令行模式下,使用 / 可以进行文件内容的搜索,而且支持正则表达式。
  • 搜索完成后,按下 Enter 键可以跳转到搜索结果的第一个匹配处。

这些是更进一步的 Vim 功能和技巧,它们可以帮助你更加高效地编辑和管理文本。继续学习和练习,你会变得更加熟练和自信地使用 Vim。

好的,我们继续学习以下关于 Vim 的功能和技巧:

  1. 文本过滤:
  • 使用 :! 命令可以在 Vim 中执行外部命令,并将其输出结果插入到当前文本中。
  • 例如,:!ls 可以列出当前目录的文件列表,并将结果插入到当前文本中。
  1. 快速注释:
  • 使用插件如 NERDCommenter 可以快速对选中的行或当前行进行注释/取消注释操作。
  • 使用 gcc 可以注释当前行,使用 gc 可以取消注释选中的行。
  1. 文本收起:
  • 使用插件如 vim-indent-guides 可以将文本按缩进进行收起,使得大段的代码更易于阅读。
  • 使用 :set foldmethod=indent 命令开启基于缩进的收起功能。
  1. 查找替换的确认模式:
  • 使用 :s/old/new/gc 可以在执行全局替换时启用确认模式,每次替换前都会进行确认。
  1. 多光标编辑:
  • 使用插件如 vim-multiple-cursors 可以在文本中同时操作多个光标,提高编辑效率。
  • 通过快捷键触发多个光标,并同时对它们进行编辑。
  1. Markdown 预览:
  • 使用插件如 vim-markdown-preview 可以在 Vim 中实时预览 Markdown 格式的文档。
  1. 文件类型检测:
  • Vim 可以根据文件内容自动检测文件类型,并相应地应用适当的语法高亮和缩进规则。
  1. 屏幕分页:
  • 使用 Ctrl + f 向前翻页,使用 Ctrl + b 向后翻页,可以在大文本文件中快速浏览。

这些是一些更高级的 Vim 功能和技巧,可以帮助你更加高效地进行文本编辑和管理。不断学习和练习,你会成为一个熟练的 Vim 用户。

当然,请继续学习以下关于 Vim 的功能和技巧:

  1. 书签操作:
  • 使用 m{a-zA-Z} 命令可以创建书签。例如,使用 ma 在当前位置创建书签 a
  • 使用 'a`a 可以跳转到书签 a 的位置。
  1. 文件对比:
  • 使用 Vim 的内置命令 vimdiff file1 file2 可以进行文件对比,显示两个文件之间的差异。
  1. 快速移动到行首和行尾:
  • 在普通模式下,按下 0 可以快速移动到当前行的行首,按下 $ 可以快速移动到当前行的行尾。
  1. 查找当前单词:
  • 在普通模式下,将光标放置在要查找的单词上,然后按下 * 可以快速查找当前单词。
  1. 隐藏和显示行号:
  • 使用 :set nu 可以显示行号,使用 :set nonu 可以隐藏行号。
  1. 自动缩进:
  • 使用 :set autoindent 可以开启自动缩进功能,使得新行的缩进与上一行相同。
  1. 自动保存:
  • 使用 :set autowrite 可以开启自动保存功能,使得在执行某些操作(如切换缓冲区或退出 Vim)时自动保存文件。
  1. 窗口自动调整大小:
  • 使用 Ctrl + w = 可以使得所有窗口等宽等高。
  • 使用 Ctrl + w | 可以使得当前窗口宽度最大化。
  • 使用 Ctrl + w _ 可以使得当前窗口高度最大化。
  1. 备份文件和交换文件:
  • Vim 可以在编辑文件时创建备份文件(以 filename~ 格式保存在同一目录下)和交换文件(以 .filename.swp 格式保存在同一目录下)。

这些是一些更高级的 Vim 功能和技巧,能够让你更加灵活和高效地进行文本编辑和管理。继续学习和练习,你会成为一个熟练的 Vim 用户。

好的,让我们继续学习以下关于 Vim 的功能和技巧:

  1. 快速跳转到指定行:
  • 在普通模式下,使用 :{line number} 可以快速跳转到指定行号。例如,:25 将光标移动到第 25 行。
  1. 查找并替换当前行内容:
  • 在普通模式下,使用 :%s/old/new/gc 可以查找并替换当前行的内容。在确认模式下 (c 选项),你可以逐个确认是否替换。
  1. 编辑命令历史记录:
  • 在命令行模式下,使用方向键可以浏览之前执行过的命令,这样你就可以轻松重复执行之前的命令。
  1. 全屏模式:
  • 在普通模式下,使用 :set fullscreen 可以进入全屏模式,最大化 Vim 窗口。
  1. 窗口高亮:
  • 在普通模式下,使用 :set cursorline 可以高亮当前光标所在行。
  1. 会话保存和加载:
  • 使用 :mksession sessionname.vim 可以将当前会话保存为一个文件。
  • 使用 vim -S sessionname.vim 可以加载之前保存的会话。
  1. 文件编码设置:
  • 在 Vim 中可以使用 :set fileencoding=utf-8 来设置文件编码格式。
  1. 文件类型切换:
  • 在普通模式下,使用 :set filetype=javascript 可以将当前文件类型设置为 JavaScript,从而启用相应的语法高亮和缩进规则。
  1. 自动对齐:
  • 在插入模式下,使用 Ctrl + ] 可以快速对齐当前行的缩进。
  1. 分页导航:
  • 在命令行模式下,使用 :set mouse=a 可以启用鼠标滚动来进行文本分页导航。

这些是更加高级的 Vim 功能和技巧,能够让你在编辑文本时更加方便和高效。继续学习并不断练习,你会变得更加熟练地使用 Vim。

继续学习以下关于 Vim 的功能和技巧:

  1. 查找当前文件中的括号匹配:
  • 在普通模式下,将光标放在一个括号上,然后按下 % 键可以快速跳转到与之匹配的括号处。
  1. 设置和查看折行(换行):
  • 使用 :set wrap 可以开启折行功能,使得长行在窗口右边折行显示。
  • 使用 :set nowrap 可以关闭折行功能。
  • 使用 :set wrap? 可以查看当前折行设置的状态。
  1. 复制、剪切和粘贴到系统剪贴板:
  • 在普通模式下,使用 "+y 可以复制文本到系统剪贴板。
  • 使用 "+x 可以剪切文本到系统剪贴板。
  • 在插入模式下,使用 Ctrl + Shift + v 可以粘贴系统剪贴板中的内容。
  1. 设置文件保存时自动格式化代码:
  • ~/.vimrc 中添加如下配置可以在保存文件时自动格式化代码:

    1
    autocmd BufWritePre *.js,*.html,*.css,*.cpp :normal gg=G

    这个命令会在保存 JavaScript、HTML、CSS 和 C++ 文件之前,执行 gg=G 命令,对整个文件进行自动格式化。

  1. 使用宏在多行上执行相同的操作:
  • 在普通模式下,可以使用宏来在多行上执行相同的操作。首先录制宏,然后在需要的地方执行宏。例如,录制宏:qq0ywA;<Esc>pq,然后执行宏 @q 在多个行的行首添加分号并跳到下一行。
  1. 自定义代码折叠:
  • 使用 :set foldmethod=manual 可以启用手动折叠模式,然后使用 zfzd 来手动创建和删除折叠。

这些是更加高级的 Vim 功能和技巧,能够让你在编辑文本时更加方便和高效。继续学习并不断练习,你会成为一个熟练的 Vim 用户。

继续学习以下关于 Vim 的功能和技巧:

  1. 使用缩写来简化输入:
  • 在 Vim 中可以使用缩写来简化输入。例如,使用 :ab 命令定义缩写。比如,:ab omw on my way 将会将 omw 自动替换为 on my way
  1. 调整窗口大小并保持纵横比:
  • 在分屏模式下,可以使用 Ctrl + W 后跟 >< 来调整窗口大小。但是如果你想保持纵横比,可以在命令前加上 z。例如,Ctrl + W z>Ctrl + W z<
  1. 在 Vim 中执行 Shell 命令:
  • 在普通模式下,使用 :! 后跟 Shell 命令可以在 Vim 中执行任何 Shell 命令。例如,:!ls 将会列出当前目录的文件列表。
  1. 标签页缩放:
  • 使用 :tabmove {N} 命令可以将当前标签页移动到指定位置。其中 {N} 是要移动到的位置索引。索引从 0 开始,0 表示最左边的标签页。
  1. 快速跳转到文件末尾:
  • 在普通模式下,使用 G 快捷键可以快速跳转到文件的末尾。
  1. 自定义快捷键映射:
  • ~/.vimrc 文件中,你可以自定义快捷键映射来执行一系列 Vim 命令。例如,:nnoremap <leader>w :w<cr> 可以将快捷键 \w 映射到保存文件的命令。
  1. 使用数字重复操作:
  • 在普通模式下,可以使用数字来重复一些操作。例如,2dd 将会删除当前行以及下一行的内容。
  1. 查看文件状态:
  • 使用 :ls 命令可以查看当前打开的所有缓冲区(文件)的列表。
  1. 跳转到文件末尾并进入插入模式:
  • 在普通模式下,使用 G 快捷键将光标跳转到文件末尾,然后再按下 o 进入插入模式,光标将会出现在文件末尾的下一行。

这些是更加高级的 Vim 功能和技巧,能够让你在编辑文本时更加方便和高效。不断学习和练习,你会变得更加熟练地使用 Vim。

继续学习以下关于 Vim 的功能和技巧:

  1. 在 Vim 中执行宏命令:
  • 如果你已经录制了一个宏,可以使用 @{register} 来执行该宏。例如,如果你录制了一个宏并将其保存在寄存器 a 中,你可以使用 @a 来执行它。
  1. 自动补全功能的使用:
  • 在插入模式下,使用 Ctrl + x 后跟 Ctrl + n 可以进行关键词补全,根据当前文件中的文本内容进行自动补全。
  1. 使用 gf 命令快速打开文件:
  • 在普通模式下,将光标移动到一个文件路径上,然后按下 gf 快捷键可以快速打开该文件。
  1. 设置文件编码格式:
  • 使用 :set fileencoding=utf-8 命令可以设置当前文件的编码格式为 UTF-8。你也可以根据需要设置其他编码格式。
  1. 标记跳转到上次编辑位置:
  • 在普通模式下,按下 快捷键可以跳转到上次编辑的位置。这个功能对于在文件中不同位置之间快速切换很有用。
  1. 快速切换到上一个缓冲区:
  • 在普通模式下,按下 Ctrl + 6 快捷键可以快速切换到上一个缓冲区。
  1. 在 Vim 中执行 Shell 命令并将结果插入到文本中:
  • 在普通模式下,使用 :r !{command} 可以在 Vim 中执行 Shell 命令,并将其结果插入到当前光标位置之后。
  1. 自动保存文件的设定:
  • 使用 :set autowrite 可以在退出 Vim 之前自动保存文件,以确保在退出 Vim 时不会丢失未保存的更改。
  1. 单词拼写检查:
  • 在普通模式下,使用 z= 快捷键可以对光标下的单词进行拼写检查,并提供可能的修正建议。
  1. 使用 Vimdiff 进行文件比较:
  • 使用 vimdiff file1 file2 命令可以在 Vim 中启动文件比较模式,方便你查看并编辑两个文件的差异。

这些是更加高级的 Vim 功能和技巧,能够让你在编辑文本时更加方便和高效。继续学习和练习,你会变得更加熟练地使用 Vim。

继续学习以下关于 Vim 的功能和技巧:

  1. 在 Vim 中执行 Python 脚本:
  • 在普通模式下,使用 :!python script.py 命令可以在 Vim 中执行 Python 脚本 script.py,并在 Vim 中查看输出结果。
  1. 使用 Ctrl + oCtrl + i 在位置之间快速切换:
  • 在普通模式下,使用 Ctrl + o 可以向后跳转到上一个光标位置,而 Ctrl + i 则可以向前跳转到下一个光标位置。
  1. 使用 :sort 对文本进行排序:
  • 在命令行模式下,使用 :sort 命令可以对当前光标所在位置以下的文本进行排序。例如,:sort 可以按字母顺序对文本进行排序。
  1. 使用 viminfo 文件保存 Vim 会话信息:
  • Vim 使用 viminfo 文件来保存会话信息,包括历史命令、搜索历史、标记位置等。你可以通过修改 ~/.vimrc 文件来自定义 viminfo 文件的保存路径和设置。
  1. 使用 zz 将光标所在行置于屏幕中央:
  • 在普通模式下,使用 zz 快捷键可以将光标所在行置于屏幕中央,方便查看光标所在行的上下文内容。
  1. 在插入模式中使用 Ctrl + w 删除前一个单词:
  • 在插入模式下,使用 Ctrl + w 快捷键可以删除光标前的一个单词,这对于纠正输入错误或者删除不需要的内容很有帮助。
  1. 在普通模式下使用 gv 重新选中上次的高亮选区:
  • 在普通模式下,按下 gv 快捷键可以重新选中上次的高亮选区,方便你对选中的文本执行其他操作,比如复制、剪切或者编辑。
  1. 使用 Ctrl + aCtrl + x 增加或减少数字:
  • 在插入模式下,按下 Ctrl + a 可以增加光标位置所在数字的值,而 Ctrl + x 则可以减少数字的值,这在编辑数字序列时非常方便。
  1. 在 Vim 中设置行间距:
  • ~/.vimrc 文件中添加如下设置可以设置 Vim 编辑器的行间距:

    1
    :set linespace=4

    这会将行间距设置为 4 个像素,你可以根据需要调整行间距的大小。

这些是更多关于 Vim 的高级功能和技巧,可以帮助你更加高效地进行文本编辑。继续学习和练习,你会变得更加熟练地使用 Vim。

继续学习以下关于 Vim 的功能和技巧:

  1. 使用 Ctrl + g 查看文件信息:
  • 在普通模式下,按下 Ctrl + g 可以显示当前文件的信息,包括文件名、行号以及文件状态等。
  1. 在普通模式下快速插入新行:
  • 在普通模式下,按下 o 可以在光标所在行的下方插入新的一行,并进入插入模式。
  • 按下 O 可以在光标所在行的上方插入新的一行,并进入插入模式。
  1. 使用 :set relativenumber 切换相对行号:
  • 使用 :set relativenumber 命令可以切换为相对行号模式,该模式下每行显示相对于光标位置的行号。
  1. 使用 Ctrl + x 进行补全:
  • 在插入模式下,按下 Ctrl + x 后,再按下 Ctrl + f 可以根据当前光标位置的单词补全文件名。
  1. 在 Vim 中执行 Ex 命令:
  • 在普通模式下,输入 : 进入命令行模式,然后输入 Ex 命令,例如 :w 保存文件,q 退出 Vim。
  1. 使用 :g 命令执行全局操作:
  • 使用 :g/pattern/cmd 可以在文件中查找匹配 pattern 的所有行,并对其执行 cmd 命令。例如,:g/TODO/d 将删除所有包含 “TODO” 的行。
  1. 使用 :earlier:later 命令在时间轴上移动:
  • 使用 :earlier {time} 命令可以将当前文件恢复到 {time} 之前的状态。而 :later {time} 可以将当前文件恢复到 {time} 之后的状态。
  1. 使用 :source 命令执行脚本文件:
  • 使用 :source {file} 命令可以执行指定的脚本文件,该文件中包含了 Vim 命令或者配置。
  1. 使用 vim -u NONE 进入纯净模式:
  • 在启动 Vim 时加上 -u NONE 参数可以进入纯净模式,不加载任何插件和配置文件,用于排除配置问题。
  1. 使用 :only 命令关闭其他窗口:
  • 使用 :only 命令可以关闭除当前窗口外的所有其他窗口,使得当前窗口独占显示。

这些是更多关于 Vim 的高级功能和技巧,可以帮助你更加高效地进行文本编辑。继续学习和练习,你会变得更加熟练地使用 Vim。

当然,请继续学习以下关于 Vim 的功能和技巧:

  1. 使用 :set list 显示不可见字符:
  • 使用 :set list 命令可以显示文档中的不可见字符,例如空格、制表符等。这对于调试格式化问题非常有用。
  1. 使用 :sort u 对文本进行唯一排序:
  • 使用 :sort u 命令可以对文本进行唯一排序,去除重复行并保持排序顺序不变。
  1. 在普通模式下移动到当前文件的顶部和底部:
  • 使用 gg 快捷键可以将光标快速移动到文件的第一行。
  • 使用 G 快捷键可以将光标快速移动到文件的最后一行。
  1. 在命令行模式下执行多个命令:
  • 在命令行模式下,使用竖线 | 分隔多个命令可以一次执行多个命令。例如,:w | !python % 可以保存当前文件并在 Python 中执行它。
  1. 使用 :retab 命令重新缩进整个文件:
  • 使用 :retab 命令可以根据当前的 tabstopshiftwidth 设置重新缩进整个文件。
  1. 在搜索模式下使用 \zs\ze 定位匹配部分:
  • 在正则表达式中,使用 \zs 表示匹配的起始位置,使用 \ze 表示匹配的结束位置。这对于定位匹配的特定部分很有用。
  1. 使用 :source 命令加载外部脚本:
  • 使用 :source {file} 命令可以加载并执行外部 Vim 脚本文件,这样可以将一些常用的配置或功能封装在脚本中,方便重用。
  1. 在 Vim 中执行宏并保存到寄存器:
  • 使用 qa 开始录制宏并将其保存到寄存器 a 中,然后执行一系列操作,最后使用 q 结束录制。
  1. 在 Vim 中设置自动缩进:
  • ~/.vimrc 文件中添加 :set autoindent 可以开启自动缩进功能,使得新行的缩进与上一行相同。
  1. 使用 :sp:vsp 打开水平或垂直分割窗口:
  • 使用 :sp {file} 可以水平分割当前窗口并打开一个新文件。而 :vsp {file} 则可以垂直分割当前窗口。

这些是更多关于 Vim 的高级功能和技巧,能够让你在编辑文本时更加方便和高效。继续学习和练习,你会变得更加熟练地使用 Vim。

1
2
3
:%s/\w\+/\U\&\E/g #将多个英文字符替换成大写

^[^\u4e00-\u9fff]* 用来匹配每一行中第一个中文字符之前的所有内容,并将其替换为空字符串,从而实现删除操作。
6.2.7 查找文件
  • find directory -name filename:在指定目录及其子目录中查找文件。
  • grep pattern filename:在文件中查找匹配指定模式的行。
6.2.8 文件权限和所有权
  • chmod permissions filename:修改文件的权限。
  • chown user:group filename:修改文件的所有者和所属组。
6.2.9 压缩和解压文件
  • gzip filenamegzip -d filename.gz:压缩或解压文件。
  • tar -cvf archive.tar files:创建 tar 归档文件。
  • tar -xvf archive.tar:解压 tar 归档文件。
6.2.10 其他操作
- `ln -s source_file symbolic_link`:创建符号链接。
- `wc filename`:统计文件的行数、单词数和字符数。
- `sort filename`:对文件内容进行排序。
- `uniq filename`:去除文件中的重复行。

这些文件操作命令可以帮助你在Unix/Linux系统中对文件进行各种常见的操作。

6.3 文件权限和用户

7. 正则表达式和模式匹配

7.1 正则表达式基础

7.2 使用 grep 进行模式匹配

7.3 使用 sed 进行文本处理

7.3.1 sed工作原理

sed(Stream Editor)是一个流编辑器,它以逐行方式处理输入流(通常是文本文件),并根据指定的编辑命令进行相应的操作。它基于一种简单而强大的编辑语言,允许用户执行搜索、替换、删除、插入等多种操作,从而对文本进行灵活的处理。

sed 的工作原理如下:

  1. 逐行处理输入流: sed 逐行读取输入流,并对每一行应用相应的编辑命令。输入流可以是文件、管道输出等。

  2. 模式匹配: sed 使用模式来匹配文本中的特定部分。用户可以使用正则表达式或简单的字符串来定义匹配模式。

  3. 编辑命令: sed 使用编辑命令来对匹配到的文本进行操作。编辑命令可以是替换命令、删除命令、插入命令等,以及一些控制流程的命令。

  4. 应用编辑命令: 一旦匹配到了指定的文本,sed 就会根据相应的编辑命令执行相应的操作。这些操作可以是替换匹配文本、删除匹配行、插入新文本等。

  5. 输出结果: sed 在处理完所有输入流后,将结果输出到标准输出(通常是终端),或者通过 -i 参数直接在原文件上进行修改。

总的来说,sed 通过一系列的模式匹配和编辑命令,实现了对文本的灵活处理和编辑。它是一种非常强大且灵活的文本处理工具,适用于各种文本编辑和处理场景。

7.3.2 sed空间

sed 中,有两个特殊的空间:模式空间(pattern space)和保留空间(hold space)。这些空间允许在处理文本时存储和操作数据。

  1. 模式空间(pattern space): 模式空间是 sed 中用于存储当前处理的行的缓冲区。当 sed 读取一行文本时,文本会被存储在模式空间中,sed 在模式空间中对其进行匹配和操作。模式空间中的内容可以被修改、替换或者通过命令输出到标准输出。默认情况下,sed 在处理完每一行后会自动清空模式空间,然后读取下一行。

  2. 保留空间(hold space): 保留空间是另一个可用于存储数据的空间,与模式空间相互独立。可以使用保留空间来存储临时数据或在处理文本时进行比较。与模式空间不同,保留空间的内容不会随着处理的文本行而变化。

通过在 sed 脚本中使用特定的命令,可以将数据从模式空间移动到保留空间,反之亦然。这使得 sed 能够在文本处理过程中灵活地使用和操作数据。

以下是一些 sed 命令,用于操作模式空间和保留空间中的数据:

  • h:将模式空间中的内容复制到保留空间。
  • H:将模式空间中的内容追加到保留空间的末尾。
  • g:将保留空间中的内容复制到模式空间。
  • G:将保留空间中的内容追加到模式空间的末尾。
  • x:交换模式空间和保留空间中的内容。

通过合理利用模式空间和保留空间,可以实现更复杂的文本处理任务,例如在 sed 中执行高级的文本转换、替换和过滤操作。

除了模式空间(Pattern Space)和保留空间(Hold Space)外,还有以下几个空间或概念:

  • 输入缓冲区(Input Buffer):这是 sed 从输入文件或标准输入读取当前行的地方。在读取一行后,sed 会将其放入模式空间中进行处理。

  • 输出缓冲区(Output Buffer):在默认情况下,sed 会将处理后的模式空间内容立即输出。但是在某些情况下(如使用 -n 选项时),输出会被暂存,直到明确调用 p 或其他输出指令,或者处理结束时才会输出。

  • 命令行缓冲区(Command Line Buffer)sed 脚本中的命令会被暂存到这个缓冲区中,随后按顺序应用于模式空间的内容。每一行在处理完毕后,sed 会继续读取下一行并重复这个过程。

  • 地址范围和上下文范围(Address and Context Range Buffers):当指定一个地址范围或上下文范围(如 sed '/start/,/end/')时,sed 会记住当前行是否在该范围内,以决定是否应用接下来的命令。

  • n:将下一行输入读取到模式空间,但不执行后续命令。

  • N:将下一行输入追加到当前模式空间的末尾。

  • P:仅打印模式空间中的第一行文本。

  • D:删除模式空间中的第一行文本,然后重新开始处理剩余的文本。

7.3.3 sed命令格式

sed 命令的基本格式如下:

1
sed [选项] '编辑命令' 文件名

其中,选项是可选的,可以用来控制 sed 的行为,编辑命令是对文件进行处理的命令,文件名是要处理的文本文件。

常用的选项包括:

  • -n:关闭自动打印模式,只有经过编辑命令处理后的行才会输出。
  • -e:指定一个或多个编辑命令。
  • -f:从指定文件中读取编辑命令。

编辑命令是对文本进行操作的指令,可以是替换、删除、插入、打印等操作,常见的编辑命令包括:

  • s/old/new/g:替换行中的 old 字符串为 new
  • /pattern/d:删除匹配到的行。
  • /pattern/i\text:在匹配到的行之前插入文本。
  • /pattern/a\text:在匹配到的行之后追加文本。
  • /pattern/p:打印匹配到的行。

例如,要将文件中的所有 foo 替换为 bar,可以使用以下命令:

1
sed 's/foo/bar/g' filename

如果要删除所有包含 pattern 的行,并将结果输出到新文件中,可以使用 -n/pattern/d 选项:

1
sed -n '/pattern/d' filename > newfile

以上是 sed 命令行的基本格式和用法,通过组合不同的选项和编辑命令,你可以实现各种文本处理操作。

7.3.4 sed指令

sed 编辑命令用于对文本进行操作,包括替换、删除、插入、追加、打印等。以下是常用的 sed 编辑命令及其说明:

  1. 替换命令 (s):

    • s/old/new/g:将行中所有的 old 替换为 new。其中 g 表示全局替换,如果不加 g,则只替换每行的第一个匹配。除了g标志,还可以是具体数字:s/old/new/2,表示替换第2个。
    • p标志打印替换的行:s/old/new/p。
    • w标志可以将更改的行保存的文件中:s/old/new/w file.txt。
    • i标志表示不区分大小写:s/old/new/i
    • 例如:sed 's/foo/bar/g' filename:将文件中所有的 foo 替换为 bar

    s命令除了可以使用/作为分隔符,还可以使用**| @ ! ^**作为分隔符

    字符替换标志:

    \L: 将紧随其后的所有字符转换为小写,直到遇到 \E 或字符串结束。

    \U: 将紧随其后的所有字符转换为大写,直到遇到 \E(结束转换)或字符串结束。

    \l: 将紧随其后的第一个字符转换为小写。

    \u: 将紧随其后的第一个字符转换为大写。

    \E:用于结束 \L\U 的作用范围。

  2. 删除命令 (d):

    • /pattern/d:删除匹配到的行。
    • 例如:sed '/pattern/d' filename:删除所有包含 pattern 的行。
  3. 插入命令 (i):

    • /pattern/i\text:在匹配到的行之前插入指定文本。
    • 例如:sed '/pattern/i\new_text' filename:在包含 pattern 的行之前插入 new_text
  4. 追加命令 (a):

    • /pattern/a\text:在匹配到的行之后追加指定文本。
    • 例如:sed '/pattern/a\new_text' filename:在包含 pattern 的行之后追加 new_text
  5. 打印命令 (p):

    • /pattern/p:打印匹配到的行。
    • 例如:sed -n '/pattern/p' filename:仅打印包含 pattern 的行。
  6. 替换命令 (y):

    • y/chars1/chars2/:将文本中 chars1 中的字符替换为 chars2 中对应位置的字符。
    • 例如:sed 'y/abc/123/' filename:将文件中所有的 a 替换为 1b 替换为 2c 替换为 3

除了上述提到的常用编辑命令外,sed 还有一些其他常用的编辑命令,包括:

  1. 行匹配命令 (\):

    • /pattern/\<命令>:对匹配到的行执行指定的命令。
    • 例如:sed '/pattern/{s/foo/bar/g}' filename:在包含 pattern 的行中,将所有的 foo 替换为 bar
  2. 行范围命令 (start,end):

    • start,end\<命令>:对指定行范围内的所有行执行指定的命令。
    • 例如:sed '2,4{s/foo/bar/g}' filename:对第 2 行到第 4 行之间的所有行,将所有的 foo 替换为 bar
  3. 行范围(start,+num):

    从start开始的,num行

  4. 行范围(start~num):

从start行开始,每num行。注意和**(start,~num)**的区别

  1. /pattern/,/pattern/或者start,/pattern/也可以混合地址和模式/pattern/,+num

  2. 行号命令 (num):

    • num\<命令>:对指定行号的行执行指定的命令。
    • 例如:sed '2{s/foo/bar/g}' filename:对第 2 行,将所有的 foo 替换为 bar
  3. 行尾追加命令 (a\):

    • $a\text:在每行的行尾追加指定文本。
    • 例如:sed '$a\End' filename:在每行的行尾追加 End
  4. 行删除命令 (d):

    • numd:删除指定行号的行。
    • 例如:sed '2d' filename:删除第 2 行。
  5. 替换行命令(c):

    替换整行

  6. 显示隐藏字符(l)

    1
    2
    sed -n 'l' wawa.txt
    sed -n 'l 5' wawa.txt #显示了个字符后换行
  7. 退出当前执行流程(q)

    请注意,quit 命令不接受地址范围,它仅支持单个地址。默认情况下,SED 遵循读取、执行和重复工作流程;但是当遇到quit命令时,它只是停止当前的执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [root@MiWiFi-RA80-srv test]# sed '3q' wawa.txt 
    name wawa
    age 6
    name feizi

    # 还可以设置退出状态码
    [root@MiWiFi-RA80-srv test]# sed '4q 10' wawa.txt
    name wawa
    age 6
    name feizi
    age 30
    [root@MiWiFi-RA80-srv test]# echo $?
    10
  8. 标签命令 (:):

    • :label:创建一个标签。
    • 例如:sed ':label' filename:创建一个名为 label 的标签。
  9. 跳转命令 (b):

    • b label:跳转到指定标签处继续执行。
    • 例如:sed '2b label' filename:如果第 2 行匹配,则跳转到名为 label 的标签处继续执行。

这些是 sed 常用的编辑命令之外的一些其他命令。通过组合这些命令,你可以实现更复杂的文本处理操作。

除了前面提到的 sed 常用编辑命令之外,还有以下一些编辑命令:

  1. 转换命令 (y):

    • y/chars1/chars2/:将文本中 chars1 中的字符替换为 chars2 中对应位置的字符。
    • 例如:sed 'y/abc/123/' filename:将文件中所有的 a 替换为 1b 替换为 2c 替换为 3
  2. 命令执行命令 (e):

    • /pattern/e command:在匹配到的行上执行指定的 shell 命令。
    • 例如:sed '/pattern/e echo "Match found"' filename:在匹配到 pattern 的行上执行 echo "Match found" 命令。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 在2,3,4,5行后附加whoami的输出内容
    [root@MiWiFi-RA80-srv test]# sed '2,5e whoami' wawa.txt
    name wawa
    root
    age 6
    root
    name feizi
    root
    age 30
    root
    name beizi
    age 36
    name zs
    age 30
  3. 打印行号命令 (=):

    • =:打印当前行号。将行号及其内容写入标准输出流
    • 例如:sed '=' filename:打印文件中每行的行号。
    • sed '$ ='filename打印文件最后一行行号,也就是文件总共多少行
  4. &命令

SED 支持特殊字符 &。每当模式匹配成功时,此特殊字符就会存储匹配的模式。它经常与替换命令一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@MiWiFi-RA80-srv test]# cat a
1 John 123456789
2 Alice 987654321
3 Bob 456789123
4 Jerry 23209009
5 Bily 230004044
6 Civi 2223394494949
[root@MiWiFi-RA80-srv test]# sed 's/[[:digit:]]/Number &/' a
Number 1 John 123456789
Number 2 Alice 987654321
Number 3 Bob 456789123
Number 4 Jerry 23209009
Number 5 Bily 230004044
Number 6 Civi 2223394494949
  1. 打印下一行命令 (N):

    • N:将下一行添加到模式空间中以供处理。
    • 例如:sed 'N;s/foo\nbar/barfoo/' filename:将匹配到 foobar 位于不同行的两行合并为一行,并将其替换为 barfoo

    默认情况下,SED 在单行上运行,但它也可以在多行上运行。多行命令用大写字母表示。例如,与 n 命令不同,N 命令不会清除并打印模式空间。相反,它会在当前模式空间的末尾添加换行符 (\n),并将输入文件中的下一行附加到当前模式空间,并通过执行其余的 SED 命令继续 SED 的标准流程。

  2. n命令

n 命令打印模式缓冲区的内容,清除模式缓冲区,将下一行提取到模式缓冲区中,然后对其应用命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@MiWiFi-RA80-srv test]# cat a
1 John 123456789
2 Alice 987654321
3 Bob 456789123
4 Jerry 23209009
5 Bily 230004044
6 Civi 2223394494949
[root@MiWiFi-RA80-srv test]# sed -n 'n;n;p' a
3 Bob 456789123
6 Civi 2223394494949
[root@MiWiFi-RA80-srv test]# sed -n 'x;n;p' a
2 Alice 987654321
4 Jerry 23209009
6 Civi 2223394494949
[root@MiWiFi-RA80-srv test]# sed -n 'x;n;x;p' a
1 John 123456789
3 Bob 456789123
5 Bily 230004044

  1. 打印模式空间命令 (p):
    • /pattern/,+1p:打印匹配到的行及其下一行。
    • 例如:sed -n '/pattern/,+1p' filename:打印包含 pattern 的行及其下一行。
  2. 读写文本命令 (rw):**
    • r filename:在模式空间中的行后面追加指定文件中的内容。
    • w filename:将模式空间中的内容写入到指定文件中。
    • 例如:sed '/pattern/r file.txt' filename:在匹配到 pattern 的行后追加 file.txt 文件中的内容。
  3. 打印模式空间第一行(P)

与 p 命令类似, P 命令来打印由 N 命令创建的多行模式空间的第一部分(直到嵌入换行符)。如果没有N,它和p命令相同。

  1. D

    D 命令会删除模式空间中的第一行,并重新开始处理剩余的模式空间。如果剩余的模式空间不为空,D 命令会跳转到脚本的开头重新执行,直到模式空间为空为止。

    这种行为允许 sed 脚本在处理多行文本时实现循环处理的效果,从而允许对整个文件进行逐行处理而不是一次性处理整个文件。

    1
    2
    sed '$!N; /^\(.*\)\n\1$/!P; D' filename
    # 删除连续重复行
  2. 检查版本(v)

    SED 还提供了一个 v 命令来检查版本。如果提供的版本高于安装的 SED 版本,则命令执行失败。请注意,此选项是 GNU 特定的,可能不适用于 SED 的其他变体。

  3. h命令

h 命令处理保持缓冲区。它将数据从模式缓冲区复制到保持缓冲区。保持缓冲区中的现有数据将被覆盖。请注意,h 命令不会移动数据,它只会复制数据。因此,复制的数据在模式缓冲器中保持原样。

1
2
3
4
5
6
7
8
9
[~/test]: cat a
1 John 123456789
2 Alice 987654321
3 Bob 456789123
4 Jerry 23209009
5 Bily 230004044
6 Civi 2223394494949
[~/test]: sed -n '/Bob/!h;/Bob/{x;p}' a
2 Alice 987654321
  1. H命令

SED 提供了 H 命令,该命令通过在末尾添加新行将内容附加到保持缓冲区。 h 和 H 命令之间的唯一区别是,前者覆盖保持缓冲区中的数据,而后者将数据追加到保持缓冲区。

1
2
3
[~/test]: sed -n '/Bob/!h;/Bob/{H;x;p}' a
2 Alice 987654321
3 Bob 456789123
  1. g命令

将数据从保持缓冲区复制到模式缓冲区。复制时,模式空间中的现有数据会被覆盖。

1
2
3
[~/test]: sed -n '/Bob/!h;/Bob/{p;g;p}' a
3 Bob 456789123
2 Alice 987654321
  1. G命令

将保持缓冲区的内容附加到模式缓冲区。 SED 提供 G 命令,通过在末尾添加新行将内容附加到模式缓冲区。

1
2
3
4
5
6
7
8
9
10
11
12
13
[~/test]: cat a
1 John 123456789
2 Alice 987654321
3 Bob 456789123
4 Jerry 23209009
5 Bily 230004044
6 Civi 2223394494949
[~/test]: sed -n '/Bob/!h;/Bob/{G;p}' a
3 Bob 456789123
2 Alice 987654321
[~/test]: sed -n '/Bob/!h;/Bob/{x;G;p}' a
2 Alice 987654321
3 Bob 456789123
  1. x命令

    交换模式空间和保持空间的内容

以上是 sed 中常用的编辑命令及其说明。通过组合这些命令,你可以实现对文本的各种操作。

与许多其他 GNU/Linux 实用程序一样,SED 也支持正则表达式,通常称为正则表达式。本章详细介绍了正则表达式。本章分为三个部分:标准正则表达式、正则表达式的 POSIX 类和元字符。

7.3.5 sed 高级用法

sed 是一个强大的文本处理工具,除了基本的替换、删除、插入等操作外,还有一些高级用法可以实现更复杂的文本处理任务。以下是一些 sed 的高级用法:

  1. 使用正则表达式:
    sed 支持正则表达式,可以在编辑命令中使用正则表达式来匹配和处理文本。这使得 sed 在文本处理中变得更加灵活和强大。

  2. 多重编辑命令:
    可以在同一条命令中使用多个编辑命令,通过分号或换行来分隔不同的编辑命令,实现一次对文本进行多个操作。

    例如:

    1
    sed -e 's/foo/bar/' -e 's/123/456/' filename
  3. 使用变量:
    可以在 sed 命令中使用变量来实现动态的文本处理操作。通常可以使用单引号或双引号将 sed 命令包裹起来,然后在其中使用变量。

    例如:

    1
    2
    3
    pattern="foo"
    replacement="bar"
    sed "s/$pattern/$replacement/g" filename
  4. 递归处理目录下的多个文件:
    使用 find 命令结合 sed,可以递归地处理目录下的多个文件。

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    find . -type f -name "*.txt" -exec sed -i 's/foo/bar/g' {} +


    在 `find` 命令中,`-exec` 选项后面可以跟随一个命令来执行。`{}` 表示 `find` 命令找到的文件名列表。`;` 表示命令的结尾,每找到一个文件就执行一次命令。`+` 则表示将找到的文件名作为参数传递给一个单独的命令,而不是每个文件都执行一次命令。

    使用 `+` 可以有效地减少 `sed` 命令的调用次数,提高效率,因为 `sed` 命令一次可以处理多个文件。相比之下,使用 `;` 每次只能处理一个文件,可能会导致 `sed` 命令被调用多次,性能较低。

    如果你使用 `\` 替代 `+`,即 `{} \;`,则相当于告诉 `find` 命令每次找到一个文件就执行一次 `sed` 命令。这种方式的效率较低,因为 `sed` 命令会被频繁地调用,不如使用 `+` 效率高。

    # 如果不递归查找,只查找1级目录,则用-maxdepty选项
    find . -maxdepth 1 -type f -name "*.txt" -exec sed -i 's/foo/bar/g' {} +

  5. 条件匹配和处理:
    可以使用条件语句来匹配和处理文本中的特定内容。例如,结合正则表达式和条件语句,可以根据不同的条件对文本进行不同的处理。

    例如:

    1
    sed '/pattern1/ s/foo/bar/; /pattern2/ s/baz/qux/' filename
  6. 使用分组和反向引用:
    结合正则表达式的分组和反向引用,可以实现更复杂的文本处理操作,例如提取文本中的特定部分或进行模式匹配替换。

    例如:

    1
    sed 's/\(pattern1\)\(pattern2\)/\2\1/' filename

以上是 sed 的一些高级用法,结合这些技巧可以实现各种复杂的文本处理任务。

除了之前提到的一些高级用法外,还有一些其他的 sed 高级用法,包括:

  1. 地址范围匹配:
    sed 支持通过地址来限定命令的作用范围。地址可以是行号、正则表达式或者地址范围。

    • 指定单行:sed '3d' filename 删除第三行。
    • 指定行范围:sed '2,5d' filename 删除第二行到第五行。
    • 使用正则表达式:sed '/pattern1/,/pattern2/d' filename 删除匹配 pattern1pattern2 之间的所有行。
  2. 命令执行顺序控制:
    可以通过花括号 {} 来定义命令块,并通过 ; 分隔不同的命令,以控制命令的执行顺序。

    例如:

    1
    2
    3
    4
    sed '/pattern/{
    s/foo/bar/
    s/baz/qux/
    }' filename
  3. 自定义标签和跳转:
    可以使用标签来标记命令块,并使用 b 命令来跳转到指定标签处执行命令。如果省略标签名称,则 SED 跳转到 SED 文件的末尾。

    例如:

    1
    2
    3
    4
    sed ':label /pattern/{
    s/foo/bar/
    b label
    }' filename
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    root~/test: cat wawa.txt 
    name wawa
    age 6
    name feizi
    age 30
    name beizi
    age 36
    name zs
    age 30

    root~/test: sed 'N;s/\n/---/;/wawa/!b la;s/^/(animal)/;:la' wawa.txt
    (animal)name wawa---age 6
    name feizi---age 30
    name beizi---age 36
    name zs---age 30

    t命令可以重新跳到标签处

  4. 扩展正则表达式:
    sed 支持 -E 选项来启用扩展正则表达式,这样可以使用更多的正则表达式语法。

    例如:

    1
    sed -E 's/(pattern1|pattern2)/replacement/' filename
  5. 删除空行:
    可以使用 sed '/^$/d' 命令来删除空行。

  6. 逆序打印:
    使用 tac 命令可以逆序打印文件内容,结合 sed 可以实现逆序打印指定范围的行。

    例如:

    1
    2
    tac filename | sed -n '5,10p' | tac
    sed -n '5,10p' filename |tac
  7. 按字段操作:
    sed 可以使用正则表达式匹配和处理字段。例如,使用 \b 来匹配单词边界,或者使用 \s 来匹配空白字符。

以上是一些 sed 的高级用法,结合这些技巧可以实现更加复杂和灵活的文本处理任务。

当然,还有更多关于 sed 的高级用法:

  1. 反转行内容:
    使用 rev 命令可以反转每一行的内容,结合 sed 可以实现反转文件内容。

    例如:

    1
    sed '1!G;h;$!d' filename
  2. 统计行数:
    可以使用 sed 命令和 wc -l 命令结合来统计文件的行数。

    例如:

    1
    sed -n '$=' filename
  3. 删除非ASCII字符:
    使用 sed 命令和正则表达式可以删除文本中的非ASCII字符。

    例如:

    1
    sed 's/[^[:print:]]//g' filename
  4. 查找并输出行号:
    使用 sed 命令和 = 命令可以输出匹配行的行号。

    例如:

    1
    sed -n '/pattern/=' filename
  5. 删除指定行:
    可以使用 sed 命令结合行号来删除指定行。

    例如:

    1
    sed -i '5d' filename
  6. 多行操作:
    通过 N 命令可以将下一行添加到模式空间,然后可以对多行进行操作。

    例如:

    1
    sed -n 'N;s/foo\nbar/baz/g;p' filename
  7. 转换大小写:
    使用 sed 命令可以轻松转换文本中的大小写。

    例如:

    1
    sed 's/foo/bar/Ig' filename  # 不区分大小写替换
  8. 在匹配行前后插入内容:
    可以使用 sed 命令在匹配行的前后插入内容。

    例如:

    1
    2
    3
    4
    5
    sed '/pattern/{i\
    Inserted text before pattern
    a\
    Inserted text after pattern
    }' filename

这些是 sed 的一些进阶用法,能够满足更多复杂的文本处理需求。结合这些技巧,你可以更加灵活地使用 sed 来处理文本数据。

  1. 还有一些转义字符\d \o \x 分别代表十进制 八进制 十六进制

还有一些POSIX类和元字符

7.3.6 sed实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@192 test]# cat a.txt 
John 123456789
Alice 987654321
Bob 456789123

[root@192 test]# sed -n '
s/^\([^ ]*\) .*/\1/ # 替换整行为第一个字段(姓名)
H # 将模式空间的内容追加到保持空间
$ { # 当到达最后一行时执行大括号内的命令
x # 交换模式空间和保持空间的内容
# s/\n//g # 删除所有换行符,将保持空间的内容合并成一行
p # 打印模式空间的内容(即所有行的第一个字段)
}
' a.txt

John
Alice
Bob
[root@192 test]# sed 's/^\([^ ]*\) .*/\1/;1!G;h;$!d' a.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@MiWiFi-RA80-srv test]# cat b.txt 
name beizi
age 36
name wawa
age 6
name zhangsan
age 22
[root@MiWiFi-RA80-srv test]# sed 'N;s/\n/,/' b.txt
name beizi,age 36
name wawa,age 6
name zhangsan,age 22
# N 命令用于将下一行添加到模式空间中,形成两行的模式空间。
# s/\n/,/ 命令用于将模式空间中的换行符替换为逗号。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@MiWiFi-RA80-srv test]# cat b.txt 
Name: John
Age: 30
Country: USA

Name: Alice
Age: 25
Country: Canada


Name: beizi
Age: 36
Country: China
[root@MiWiFi-RA80-srv test]# sed '/^$/d;N;N;s/\n/,/g' b.txt
Name: John,Age: 30,Country: USA
Name: Alice,Age: 25,Country: Canada
Name: beizi,Age: 36,Country: China

# /^$/d 命令用于删除空行。
# N 命令用于将下一行添加到模式空间中,形成两行的模式空间。
# s/\n/,/g 命令用于将模式空间中的换行符替换为逗号,这里使用了 g 参数,表示全局替换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
```
This is line 1
This is line 2
This is line 3
```

我们希望将每行文本合并成一行,并在每行之间添加分隔符,例如逗号。我们可以使用 `sed` 来实现这个目标。

```bash
sed ':a;N;$!ba;s/\n/, /g' multiline.txt
```

这个命令的含义是:
- `:a` 声明一个标签 `a`。
- `N` 命令用于将下一行添加到模式空间中,形成多行的模式空间。
- `$!ba` 命令将控制流程跳转到标签 `a`,在模式空间中的所有行被处理完毕后。
- `s/\n/, /g` 命令用于将模式空间中的换行符替换为逗号和空格,这里使用了 `g` 参数,表示全局替换。

运行上述命令后,输出如下:

```
This is line 1, This is line 2, This is line 3
```

这样,我们就成功地将多行文本合并为单行,并在每行之间添加了逗号和空格作为分隔符。

8. 高级命令和工具

8.1 awkcut 命令

了解 awk 的详细内容将涵盖其语法、内置变量、操作符、函数、控制结构等方面。

8.1.1. awk 语法
  • awk 命令的基本语法结构。

awk 的基本语法结构由模式-动作组成。下面是 awk 基本语法的概述:

1
awk 'pattern { action }' filename
  • pattern 是一个模式,用于匹配输入数据中的行。
  • { action } 是一个动作块,当输入的行匹配模式时,将执行其中的动作。
  • filename 是要处理的文件名。如果不指定文件名,则默认从标准输入(stdin)读取数据。

pattern{ action } 都可以省略,但至少要有一个。如果省略了 pattern,则会应用于输入中的每一行。如果省略了 { action },则默认执行 { print },即打印匹配到的行。

下面是一些示例 awk 命令的用法:

  • 打印文件中的所有行:
1
awk '{ print }' filename
  • 打印文件中包含 “pattern” 的行:
1
awk '/pattern/ { print }' filename
  • 在模式和动作之间使用多个动作,以分号分隔:
1
awk '/pattern/ { print $1, $2; sum += $3 } END { print "Total:", sum }' filename

这些示例展示了 awk 基本语法的使用,可以根据需要自定义模式和动作来处理文本数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 示例1:打印文件中的所有行
awk '{ print }' filename

# 示例2:打印包含特定模式的行,并计算行数
awk '/pattern/ { print; count++ } END { print "Total lines:", count }' filename

# 示例3:打印第一列和第二列,并计算第三列的总和
awk '{ print $1, $2; sum += $3 } END { print "Total:", sum }' filename

# 示例4:统计文件中每个单词的出现次数
awk '{ for(i=1; i<=NF; i++) words[$i]++ } END { for(w in words) print w, words[w] }' filename

# 示例5:根据条件筛选行并打印特定字段
awk '$3 > 50 { print $1, $2 }' filename

# 示例6:在输出中添加自定义格式
awk '{ printf "%-10s %-8s %4s\n", $1, $2, $3 }' filename

# 示例7:打印文件中的奇数行
awk 'NR % 2 == 1' filename

# 示例8:删除文件中的空行
awk NF filename

# 示例9:计算文件中每行字符数的平均值
awk '{ total += length($0) } END { print "Average line length:", total/NR }' filename

# 示例10:将数字字段按逆序输出
awk '{ for(i=NF; i>=1; i--) printf "%s ", $i; print "" }' filename

  • 模式-动作语句的组织方式。

awk 中,模式-动作语句是一种用于指定在匹配到特定模式时执行的动作的结构。以下是模式-动作语句的组织方式:

1
awk 'pattern1 { action1 } pattern2 { action2 } ... patternN { actionN }' filename
  • 每个模式-动作语句由一个模式和一个动作组成,它们被大括号 {} 包围。
  • 模式用于匹配输入的行,如果匹配成功,则执行与之关联的动作。
  • 动作是在匹配到模式时执行的操作,可以是任意合法的 awk 语句或命令序列。

在模式-动作语句中,可以有一个或多个模式-动作对,它们按顺序应用于输入的每一行。如果省略了模式,则相应的动作将应用于每一行。

以下是一个示例,演示了模式-动作语句的组织方式:

1
2
3
4
5
6
7
8
# 如果第一列的值大于10,则打印该行
awk '$1 > 10 { print }' filename

# 如果第二列的值包含 "pattern",则打印该行
awk '$2 ~ /pattern/ { print }' filename

# 如果第一列的值小于5,则将第二列的值累加并打印总和
awk '$1 < 5 { sum += $2 } END { print "Sum:", sum }' filename

在这些示例中,每个模式-动作语句都包含一个模式和一个动作。模式用于选择要操作的行,而动作则指定在匹配到模式时要执行的操作。

  • 使用单引号或双引号来编写 awk 程序。

awk 中,可以使用单引号或双引号来编写程序。这两种引号的选择主要取决于需要在程序中包含的变量和特殊字符。

使用单引号 '

1
awk 'pattern { action }' filename
  • 单引号中的内容被视为一个整体字符串,不会对其中的变量进行扩展或替换。
  • 这意味着在单引号中无法直接引用Shell变量或进行变量替换。
  • 使用单引号时,变量应当在外部传入,而不是在 awk 程序中定义。

例:

1
awk '$1 > 10 { print }' filename

在这个示例中,单引号内的程序以字符串形式传递给 awk,其中的 $1print 作为 awk 的语法被解释,而不会被Shell进行变量替换。

使用双引号 "

1
awk "pattern { action }" filename
  • 双引号中的内容会被Shell解释器进行扩展和替换。
  • 这意味着可以在双引号中引用Shell变量,并进行变量替换。
  • 使用双引号时,应注意转义字符的处理,以避免对特殊字符的意外解释。

例:

1
awk "END { print \"Total lines: \", NR }" filename

在这个示例中,双引号内的程序中的 NR 是一个 awk 内置变量,但是双引号使得Shell将其扩展为行数,然后再传递给 awk。由于 "\ 在双引号内有特殊含义,因此需要进行转义以确保它们被 awk 正确解释。

8.1.2. 内置变量
  • awk 内置变量的介绍,如 NFNRFSRS 等。

在 AWK 中,有一些内置变量可以在程序中使用。这些变量提供了对输入、输出、行号等信息的访问。以下是一些常用的 AWK 内置变量:

  1. **$0**:当前记录的内容(整行)。
  2. **$1, $2, …, $NF**:当前记录的第 1、2、…、最后一个字段。
  3. **NF**:当前记录中字段的数量。
  4. **NR**:已读取的记录数(行号)。
  5. **FNR**:当前文件中已读取的记录数(行号)。
1
2
3
NR:表示当前正在处理的记录数(即行数),它从AWK开始执行以来逐行递增,即使处理多个文件,NR也会逐行增加,它不会重置。
FNR:表示当前正在处理的文件中的记录数(即行数),当AWK处理多个文件时,FNR会在每个新文件中重新开始计数,即处理新文件时FNR会重置为1。
通常情况下,当您只处理一个文件时,NR和FNR的值相等,因为它们都是当前处理的行数。但是当您处理多个文件时,FNR会在每个新文件中重新开始计数,而NR会继续递增。因此,当您需要在处理多个文件时保持对每个文件中行数的跟踪时,就会用到FNR
  1. **FILENAME**:当前输入文件的名称。
  2. **FS**:字段分隔符(默认为空格或制表符)。
  3. **RS**:记录分隔符(默认为换行符)。
  4. **OFS**:输出字段分隔符(默认为空格)。
  5. **ORS**:输出记录分隔符(默认为换行符)。

这些内置变量可以在 awk 程序中直接使用,用于访问和处理输入数据。例如:

1
2
3
4
5
6
7
8
9
10
11
# 打印文件的第一行
awk 'NR == 1 { print }' filename

# 打印文件的最后一行
awk 'END { print }' filename

# 打印每行的行号和内容
awk '{ print NR, $0 }' filename

# 打印每行的字段数量
awk '{ print NF }' filename

通过使用这些内置变量,可以编写出更加灵活和强大的 awk 程序,用于处理各种文本数据。

还有一些内置变量及其含义:

  1. **ARGC**:命令行参数的数量。
  2. **ARGV**:一个数组,包含命令行参数的内容。
  3. **ENVIRON**:一个数组,包含环境变量的内容。
  4. **OFMT**:数字的输出格式(默认为 “%.6g”)。
  5. **RLENGTH**:由 match 函数设置的匹配到的子串的长度。
  6. **SUBSEP**:多维数组的分隔符(默认为 “\034”)。

这些内置变量提供了对输入、输出、环境以及处理过程的访问和控制。使用这些变量,你可以更加灵活地处理文本数据,并实现各种复杂的文本处理任务。

  • 了解这些变量的含义和用法,以及它们在 awk 程序中的作用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
以下是 AWK 中常用的内置变量及其含义、用法和作用:

1. **`ARGC`**
- **含义**:命令行参数的数量。
- **用法**:通常用于确定命令行中指定了多少个参数。
- **作用**:可以根据命令行参数的数量,编写适应不同情况的 `awk` 程序。

2. **`ARGV`**
- **含义**:一个数组,包含命令行参数的内容。
- **用法**:可以通过遍历该数组,获取命令行中指定的每个参数。
- **作用**:通常用于处理命令行传入的参数,例如处理文件名列表等。

3. **`ENVIRON`**
- **含义**:一个数组,包含环境变量的内容。
- **用法**:可以通过该数组访问系统环境变量的值。
- **作用**:常用于在 `awk` 程序中获取系统环境变量的值,或者进行环境相关的处理。

4. **`FILENAME`**
- **含义**:当前输入文件的名称。
- **用法**`FILENAME` 变量包含当前正在处理的文件名。
- **作用**:通常用于在处理多个文件时,区分当前正在处理的文件。

5. **`FNR`**
- **含义**:当前文件中已读取的记录数(行号)。
- **用法**`FNR` 变量用于跟踪当前文件中的行号。
- **作用**:常用于在处理多个文件时,获取每个文件中的行号。

6. **`FS`**
- **含义**:字段分隔符(默认为空格或制表符)。
- **用法**:可以通过修改 `FS` 的值,改变字段的分隔符。
- **作用**:控制 `awk` 如何将输入的记录分割成字段。

7. **`NF`**
- **含义**:当前记录中字段的数量。
- **用法**`NF` 变量包含当前记录中的字段数。
- **作用**:常用于在处理每行数据时,确定字段的数量。

8. **`NR`**
- **含义**:已读取的记录数(行号)。
- **用法**`NR` 变量用于跟踪已经读取的行数。
- **作用**:常用于在处理输入时,获取整个输入流的行号。

9. **`OFS`**
- **含义**:输出字段分隔符(默认为空格)。
- **用法**:可以通过修改 `OFS` 的值,改变输出的字段分隔符。
- **作用**:控制 `awk` 在输出时,字段之间的分隔符。

10. **`ORS`**
- **含义**:输出记录分隔符(默认为换行符)。
- **用法**:可以通过修改 `ORS` 的值,改变输出的记录分隔符。
- **作用**:控制 `awk` 在输出时,记录之间的分隔符。

11. **`OFMT`**
- **含义**:数字的输出格式(默认为 "%.6g")。
- **用法**:可以通过修改 `OFMT` 的值,改变数字的输出格式。
- **作用**:控制 `awk` 在输出数字时的格式,包括小数位数等。

12. **`RLENGTH`**
- **含义**:由 `match` 函数设置的匹配到的子串的长度。
- **用法**`RLENGTH` 变量包含由 `match` 函数找到的匹配子串的长度。
- **作用**:常用于获取 `match` 函数匹配到的子串的长度。

13. **`RS`**
- **含义**:记录分隔符(默认为换行符)。
- **用法**:可以通过修改 `RS` 的值,改变记录的分隔符。
- **作用**:控制 `awk` 在处理输入时,如何识别记录。

14. **`SUBSEP`**
- **含义**:多维数组的分隔符(默认为 "\034")。
- **用法**`SUBSEP` 变量通常在多维数组中使用,用于分隔不同维度的索引。
- **作用**:允许创建和操作多维数组。

这些内置变量提供了对输入、输出、环境以及处理过程的访问和控制。通过使用这些变量,你可以更加灵活地处理文本数据,并实现各种复杂的文本处理任务。
8.1.3. 操作符
  • awk 中支持的各种操作符,包括赋值操作符、算术操作符、比较操作符和逻辑操作符。

awk 中支持多种操作符,用于对数据进行比较、计算和逻辑操作。以下是 awk 中支持的各种操作符:

算术操作符

  1. **+**:加法。
  2. **-**:减法。
  3. *****:乘法。
  4. **/**:除法。
  5. **%**:取模(取余)。

关系操作符

  1. **==**:等于。
  2. **!=**:不等于。
  3. **>**:大于。
  4. **<**:小于。
  5. **>=**:大于等于。
  6. **<=**:小于等于。

赋值操作符

  1. **=**:赋值。
  2. **+=**:加法赋值。
  3. **-=**:减法赋值。
  4. ***=**:乘法赋值。
  5. **/=**:除法赋值。
  6. **%=**:取模赋值。

逻辑操作符

  1. **&&**:逻辑与。
  2. **||**:逻辑或。
  3. **!**:逻辑非。

拼接操作符

  1. ****:空格,用于拼接字符串。
  2. **$0**:整行拼接,将所有字段连接成一个字符串。

正则表达式操作符

  1. **~**:匹配正则表达式。
  2. **!~**:不匹配正则表达式。

字段操作符

  1. **$1**:第一个字段。
  2. **$2**:第二个字段。
  3. **$NF**:最后一个字段。

其他操作符

  1. **?:**:条件表达式。
  2. **,**:逗号操作符,用于分隔表达式。

这些操作符可以组合使用,用于对数据进行各种比较、计算和逻辑操作,从而实现复杂的文本处理任务。

  • 了解这些操作符的优先级和结合性,以及如何在 awk 程序中使用它们。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
`awk` 中,操作符的优先级和结合性决定了表达式中各个操作符的计算顺序。以下是 `awk` 中常见操作符的优先级和结合性:

### 优先级

1. 高优先级
2. 乘法 `*`、除法 `/`、取模 `%`
3. 加法 `+`、减法 `-`
4. 关系操作符 `==`, `!=`, `>`, `<`, `>=`, `<=`, `~`, `!~`
5. 逻辑与 `&&`
6. 逻辑或 `||`
7. 逗号 `,`
8. 低优先级

### 结合性

1. 左结合性:操作符从左到右计算。
2. 右结合性:操作符从右到左计算。

根据优先级和结合性,`awk` 将会按照上述顺序计算表达式中的各个操作符。可以通过使用括号来改变默认的优先级和结合性。

例如,考虑以下表达式:

```awk
result = a + b * c - d / e
```

根据优先级和结合性,该表达式的计算顺序为:

1. 先乘除后加减,即 `b * c``d / e` 的计算。
2. 然后将乘除结果与加减结果相加减,即 `a + (b * c) - (d / e)`

如果需要改变计算顺序,可以使用括号来明确表达式的含义,例如:

```awk
result = (a + b) * (c - d) / e
```

在这个表达式中,括号明确了加减运算的优先级,改变了默认的计算顺序。
  1. awk中也可以使用重定向 > 或 >>

它将数据附加到输出文件中。如果输出文件不存在,则会创建一个。使用这种类型的重定向时,新内容将附加在文件末尾。例如,以下示例附加 Hello, World !!!到文件。

1
2
3
print DATA > output-file
print DATA >> output-file
awk 'BEGIN { print "Hello, World !!!" > "/tmp/message.txt" }' #重定向>后的文件要用双引号括起来

31.awk中也可以使用管道 |或双向通信 |&

可以通过管道而不是使用文件将输出发送到另一个程序。此重定向打开一个命令管道,并通过该管道将项目的值写入另一个进程以执行该命令。重定向参数命令实际上是一个 AWK 表达式。

1
2
3
4
5
6
7
8
9
10
11
awk 'BEGIN { print "hello, world !!!" | "tr [a-z] [A-Z]" }' # HELLO, WORLD !!!

awk 'BEGIN {
cmd = "tr [a-z] [A-Z]"
print "hello, world !!!" |& cmd
close(cmd, "to")

cmd |& getline out
print out;
close(cmd);
}'
8.1.4. 函数
  • awk 中内置的一些常用函数,如字符串函数、数学函数等。

awk 内置了许多函数,这些函数可以用于对文本数据进行各种操作,包括字符串处理、数值计算、数组操作等。以下是一些常用的 awk 内置函数:

  1. 字符串函数

    • **length(s)**:返回字符串 s 的长度。

    • **substr(s, m, n)**:返回字符串 s 中从位置 m 开始的长度为 n 的子串。如果没指定n则从m开始到结束

      1
      2
      3
      4
      bz🤪 awk 'BEGIN{print substr("loveyou",1,3)}'
      lov
      bz🤪 awk 'BEGIN{print substr("loveyou",1+3)}'
      eyou
    • **index(s1, s2)**:返回字符串 s1 中第一次出现字符串 s2 的位置(从1开始),如果未找到则返回0。

    • **tolower(s)**:将字符串 s 转换为小写形式。

    • **toupper(s)**:将字符串 s 转换为大写形式。

    1
    2
    3
    4
    5
    bz🤪 cat a.txt 
    i love you
    i like you
    server78
    server87
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    bz🤪 awk '{print length($0), $0}' a.txt 
    10 i love you
    10 i like you
    8 server78
    8 server87

    bz🤪 awk '{print substr($0,3,3)}' a.txt
    lov
    lik
    rve
    rve

    bz🤪 awk '{print index($0,"e")}' a.txt
    6
    6
    2
    2

    bz🤪 awk '{print toupper($0)}' a.txt
    I LOVE YOU
    I LIKE YOU
    SERVER78
    SERVER87

    除了我之前提到的字符串处理函数外,AWK 还提供了其他一些常用的字符串函数,以下是其中一些:

    1. 匹配函数

      • match(string, pattern, array)
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24

        这个函数用于在字符串 `string` 中查找匹配 `pattern` 的子字符串,并将结果存储在数组 `array` 中。如果未提供数组参数,则返回匹配的起始位置。`pattern` 是正则表达式。

        - `match(str, regexp)`:在字符串 `str` 中查找正则表达式 `regexp` 的第一个匹配项,并返回匹配的起始位置。如果找到匹配项,则返回其起始位置;否则返回 0。

        `RSTART` 和 `RLENGTH` 是 AWK 中的两个特殊变量,用于存储 `match()` 函数的匹配结果。

        - `RSTART`:表示最近一次 `match()` 函数匹配的子字符串在目标字符串中的起始位置(索引从 1 开始)。如果没有匹配成功,则 `RSTART` 的值为 0。
        - `RLENGTH`:表示最近一次 `match()` 函数匹配的子字符串的长度。如果没有匹配成功,则 `RLENGTH` 的值为 0。

        这两个变量通常与 `match()` 函数一起使用,以获取匹配子字符串的位置和长度信息。在之前提到的第二种方法中,我们使用了 `RSTART` 和 `RLENGTH` 来确定每次匹配到的数字的位置和长度,并通过 `substr()` 函数来提取这些数字,然后将其累加到总和中。



        - `sub(regexp, replacement, target)`:在字符串 `target` 中查找正则表达式 `regexp` 的第一个匹配项,并用 `replacement` 替换。返回替换次数(0 或 1)。

        - `gsub(regexp, replacement, target)`:在字符串 `target` 中查找所有匹配正则表达式 `regexp` 的项,并用 `replacement` 替换。返回替换次数。

        Awk中sub()实例

        **sub()函数**用于在字符串中进行**一次**替换操作。它只替换第一个匹配到的模式,并且不会继续搜索或替换其他匹配项。

        **语法格式:**

        sub(pattern, replacement, string)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        **参数说明:**

        - `pattern`: 要匹配的模式,可以是正则表达式。
        - `replacement`: 替换字符串。
        - `string`: 要进行替换操作的字符串。

        **示例:**

        1. 将字符串中的第一个空格替换为下划线:

        awk '{sub(" ", "_", $0); print}' file.txt
        1
        2
        3

        1. 将字符串中的所有数字替换为星号:

        awk '{sub("[0-9]", "*", $0); print}' file.txt
        1
        2
        3

        1. 将字符串中的第一个"abc"替换为"xyz":

        awk '{sub("abc", "xyz", $0); print}' file.txt
        1
        2
        3

        1. 将字符串中的所有"abc"替换为"xyz",并忽略大小写:

        awk '{sub(/abc/i, "xyz", $0); print}' file.txt
        1
        2
        3
        4
        5

        **更多示例:**

        - 将字符串中的所有空格替换为空字符串:

        awk '{gsub(" ", "", $0); print}' file.txt
        1
        2
        3

        - 将字符串中的所有数字替换为"0":

        awk '{gsub("[0-9]", "0", $0); print}' file.txt
        1
        2
        3

        - 将字符串中的所有字母替换为大写:

        awk '{gsub(/[a-z]/, toupper($0), $0); print}' file.txt
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89

        **注意:**

        - sub()函数只替换第一个匹配到的模式,如果要替换所有匹配项,可以使用**gsub()函数**。
        - sub()函数和gsub()函数都可以使用正则表达式进行模式匹配。



        2. **格式化函数**:

        - `sprintf(format, expr1, expr2, ...)`:根据指定的格式 `format` 返回格式化后的字符串。类似于 C 语言中的 `sprintf` 函数。

        3. **查找函数**:

        - `index(str, search)`:在字符串 `str` 中查找子字符串 `search` 的第一个匹配项,并返回其起始位置。如果找到匹配项,则返回其起始位置;否则返回 0。
        - `tolower(str)`、`toupper(str)`:分别将字符串 `str` 转换为小写或大写。

        4. **拼接函数**:

        - `concat(str1, str2, ...)`:将多个字符串 `str1`、`str2` 等拼接成一个字符串。

        5. **分割函数**:

        - `split(str, array, separator)`:将字符串 `str` 按照分隔符 `separator` 进行拆分,并将拆分后的子字符串保存到数组 `array` 中。返回拆分后的子字符串个数。

        6. **去除空白函数**:

        - `sub()`、`gsub()` 也可以用于去除字符串中的空白字符,例如 `sub(/^ +| +$/, "", str)` 可以去除字符串 `str` 前后的空格。

        这些字符串函数可以帮助您在 AWK 中进行更复杂的文本处理任务,例如查找匹配项、替换子字符串、格式化输出等。

        2. **数值函数**:

        - **`sqrt(x)`**:返回 `x` 的平方根。
        - **`int(x)`**:返回 `x` 的整数部分。
        - **`rand()`**:返回0到1之间的随机数。
        - **`srand([x])`**:设置随机数种子。如果不提供参数 `x`,则使用系统时间作为种子。

        3. **数组函数**:

        - **`delete array[index]`**:删除数组 `array` 中指定索引的元素。

        - **`delete array`**:删除数组 `array` 的所有元素。

        - **`length(array)`**:返回数组 `array` 的长度。

        - **`split(str, arr, [sep])`**:将字符串 `str` 按分隔符 `sep` 分割成数组 `arr` 的元素。

        Awk 中的 `split()` 函数用于将字符串分割成数组。它接受三个参数:

        - **str**:要分割的字符串。
        - **array**:存储分割结果的数组。
        - **sep**:分割符。

        `split()` 函数会将 `str` 中的每个子字符串(由 `sep` 分隔)存储在 `array` 数组中。数组的下标从 1 开始,第一个元素是分割符前的字符串,后续元素是分割符后的字符串。

        ```bash
        bz🤪 awk 'BEGIN{str="i love you,you,love me";split (str,words," ")
        for (i=1; i<=length(words); i++)
        print words[i]
        }'
        i
        love
        you,you,love
        me
        bz🤪 awk 'BEGIN{str="i love you,you,love me";split (str,words,"[ ,]")
        for (i=1; i<=length(words); i++)
        print words[i]
        }'
        i
        love
        you
        you
        love
        me
        bz🤪 awk 'BEGIN{str="this is test: 1 2 3"
        split(str, words," ")
        for (i=1;i<=length(words);i++)
        if (match(words[i], /[0-9]+/)) print words[i]
        }'
        1
        2
        3
        bz🤪 awk 'BEGIN {
        str = "/path/to/file.txt"
        split(str,array, "/")
        print array[length(array)]
        }'
        file.txt
  2. 日期函数

    • systime():返回当前时间的秒数(从 1970 年 1 月 1 日至今的秒数)。
    • strftime(format, timestamp):将时间戳 timestamp 格式化为指定的日期/时间格式
  3. 输出函数

    • **print items**:输出指定的项,以空格分隔。
    • **printf format, items**:按照指定的格式输出指定的项。
  4. 文件函数

    • **getline**:从输入中读取下一行。
    • **getline var**:从输入中读取下一行,并将其保存到变量 var 中。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    bz🤪 cat b.txt 
    name age
    aa 000
    bb 999
    cc 888
    ac 222
    bz🤪awk '{getline a; print a}'t b.txt
    aa 000
    cc 888
    cc 888
    • **close(filename)**:关闭文件。

这些函数提供了丰富的功能,可以用于处理各种文本数据和执行各种计算。通过灵活运用这些内置函数,可以编写出功能强大的 awk 程序来满足不同的需求。

  • 自定义函数的创建和使用方法,以及如何在 awk 程序中调用它们。
Awk中自定义函数

Awk允许用户定义自定义函数,以便将代码进行重用,提高代码的可读性和可维护性。

Awk

1
2
3
function function_name(parameter1, parameter2, ...) {
# 函数体
}

参数说明:

  • function_name: 函数名称,由字母、数字、下划线组成,不能以数字开头。
  • parameter1, parameter2, …: 函数参数,可以是多个。
  • 函数体: 函数的代码块,包含要执行的语句。

示例:

定义一个函数,用于计算两个数的平方和:

Awk

1
2
3
4
5
function square_sum(a, b) {
return a^2 + b^2
}

{print square_sum($1, $2)}

示例输入:

1
1 2

示例输出:

1
5

调用自定义函数:

在awk程序中,可以使用函数名称和参数来调用自定义函数。

示例:

Awk

1
2
3
4
5
function print_message(message) {
print message
}

print_message("Hello, world!")

示例输出:

1
Hello, world!

自定义函数的优势:

  • 提高代码的重用性,避免重复编写代码。
  • 提高代码的可读性和可维护性,使代码更加清晰易懂。
  • 可以将复杂的操作封装成函数,方便调用。

注意事项:

  • 函数名称不能与awk内置函数同名。
  • 函数参数必须在函数体中使用。
  • 函数的返回值可以使用return语句返回。

以下是一些使用自定义函数的示例:

  • 计算文件的行数:

Awk

1
2
3
4
5
6
7
8
function count_lines(filename) {
while ((getline line < filename) > 0) {
count++
}
return count
}

{print count_lines("myfile.txt")}
  • 将字符串中的所有空格替换为下划线:

Awk

1
2
3
4
5
function replace_spaces(str) {
return gsub(" ", "_", str)
}

{print replace_spaces($0)}
  • 将字符串中的所有数字转换为大写:

Awk

1
2
3
4
5
6
7
8
9
10
function to_upper(str) {
for (i = 1; i <= length(str); i++) {
if (str[i] ~ /[0-9]/) {
str[i] = toupper(str[i])
}
}
return str
}

{print to_upper($0)}
8.1.5. 控制结构
  • awk 中支持的各种控制结构,包括条件语句和循环语句。
  • 了解如何在 awk 程序中使用 if-else 语句、while 循环、for 循环等。
8.1.6. 输入处理
  • 如何使用 awk 处理输入数据,包括文件输入和管道输入。
  • 使用 awk 内置变量和函数来处理输入数据,如 getline 函数和 ARGV 数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 计算文字中所有数字之和
bz🤪 cat a.txt
pple 10 r 30
banana 15
orange 20 fg 3
bz🤪awk '{split($0,nums," ");for (i=1;i<=length(nums);i++) {
if (match (nums[i],/[0-9]+/)) sum+=nums[i]
}}
END{print sum}' a.txt
78

bz🤪 awk '{
# 使用正则表达式匹配数字,并将其累加到总和 sum 中
for (i=1; i<=NF; i++) {
if ($i ~ /^[0-9]+$/) {
sum += $i;
}
}
}
END {
# 在处理完所有行后输出总和
print "总和为:", sum;
}' a.txt
78
# 上面的都需要数字与其他字符分开,下面的方法,只要是数字就能累加
bz🤪 awk '{
# 使用循环和 match() 函数查找所有数字并累加
while (match($0, /[0-9]+/)) {
sum += substr($0, RSTART, RLENGTH);
$0 = substr($0, RSTART + RLENGTH);
}
}
END {
print "总和为:", sum;
}' a.txt
总和为: 78

8.1.7. 输出格式化
1
2
3
4
5
6
7
8
awk转义序列:

\n:换行符
\t:制表符
\\:反斜杠(用于转义)
\":双引号(用于转义)
\0:空字符(ASCII码0)
\xhh:十六进制字符表示,其中hh是两个十六进制数字,表示ASCII码值
  • awk 中如何格式化输出结果,包括字段分隔符、输出宽度和对齐方式等。
  • 使用 printf 函数进行复杂输出格式控制。
8.1.8. 高级功能
  • awk 中的高级功能,如数组的使用、多维数组、递归函数等。
  • 如何利用这些高级功能解决复杂的文本处理问题。
8.1.9. 实例演示
  • 使用实际示例演示 awk 的使用,包括日志分析、数据转换、报告生成等常见任务。
  • 通过实例帮助理解 awk 的实际应用场景和解决方案。
8.1.10. 调试和优化
  • awk 程序的调试技巧,如打印调试信息、使用 trace 功能等。
  • 如何优化 awk 程序,提高性能和效率。

通过深入了解 awk 的语法和功能,你将能够更加熟练地使用它来处理各种文本数据,并解决实际的问题。

8.2 findxargs 命令

8.3 sortuniq 命令

9. 脚本调试和错误处理

  • 9.1 使用 set 调试脚本
  • 9.2 错误处理和退出状态码
  • 9.3 使用 trap 处理信号
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
当然,我会对每种方法进行详细解析:

### 1. 打印调试信息:
在脚本中插入 `echo` 语句是最简单直接的调试方法之一。通过输出变量的值、程序执行到的位置等信息,可以帮助你了解程序执行时的状态和流程,方便排查问题。

示例:
```bash
#!/bin/bash

# 定义变量
x=10

# 打印调试信息
echo "Variable x = $x"
echo "Reached line 10"
```

在执行脚本时,会输出相应的调试信息,帮助你了解程序执行的情况。

### 2. 使用 `set -x` 和 `set +x`:
`set -x` 命令启用了调试模式,会显示执行的每一行命令。而 `set +x` 命令则关闭调试模式。这在需要调试特定代码段时非常有用,避免在整个脚本中都输出调试信息。

示例:
```bash
#!/bin/bash

# 启用调试模式
set -x

# 要调试的代码段...
x=10
echo "Variable x = $x"

# 关闭调试模式
set +x
```

在执行脚本时,会显示每一行命令的执行情况,以帮助你跟踪代码的执行过程。

### 3. 使用 `set -e`:
`set -e` 命令会使得脚本在遇到错误时立即退出。这有助于在脚本发生错误时快速停止执行,避免错误继续扩大。

示例:
```bash
#!/bin/bash

# 启用错误检查
set -e

# 脚本内容...
ls non_existent_file # 这一行会导致脚本退出
echo "This line will not be executed"
```

在执行脚本时,如果 `ls non_existent_file` 命令返回非零退出状态,脚本会立即退出,后续的代码不会执行。

### 4. 使用 `trap` 命令处理错误:
`trap` 命令可以捕获信号,并执行相应的处理操作。当使用 `ERR` 信号时,可以捕获脚本中发生的错误,并执行指定的处理函数。

示例:
```bash
#!/bin/bash

# 捕获错误信号并处理
handle_error() {
echo "Error occurred in line $1."
exit 1
}
trap 'handle_error $LINENO' ERR

# 脚本内容...
ls non_existent_file # 这一行会导致错误
echo "This line will not be executed"
```

在执行脚本时,如果 `ls non_existent_file` 命令返回非零退出状态,会执行 `handle_error` 函数,并输出相应的错误信息。

### 5. 使用 `set -o errexit` 和 `set -o nounset`:
`set -o errexit` 会开启脚本的退出状态检查,当命令返回非零退出状态时,脚本会立即退出。而 `set -o nounset` 会开启未定义变量检查,当使用未定义的变量时,脚本会立即退出。

示例:
```bash
#!/bin/bash

set -o errexit # 启用退出状态检查
set -o nounset # 启用未定义变量检查

# 脚本内容...
ls non_existent_file # 这一行会导致脚本退出
echo "This line will not be executed"
```

在执行脚本时,如果 `ls non_existent_file` 命令返回非零退出状态,或者在脚本中使用了未定义的变量,脚本会立即退出。

### 6. 日志记录:
在脚本中添加日志记录功能,可以使用 `echo` 将脚本执行过程中的重要信息写入日志文件,以便后续分析和排查问题。

示例:
```bash
#!/bin/bash

LOG_FILE="/var/log/my_script.log"

# 记录日志
log() {
echo "$(date +"%Y-%m-%d %H:%M:%S") $1" >> "$LOG_FILE"
}

# 脚本内容...
log "Starting script..."
ls non_existent_file # 这一行会导致错误
log "This line will not be executed"
```

在执行脚本时,会将执行过程中的重要信息写入日志文件中,方便后续排查问题。

### 7. 使用 `debug` 命令:
在需要调试的代码段前后分别添加 `set -x` 和 `set +x` 命令,可以只在特定代码段开启调试模式,便于精确定位问题。

示例:
```bash
#!/bin/bash

# 脚本内容...

# 启用调试模式
set -x
# 要调试的代码段...
x=10
echo "Variable x = $x"
# 关闭调试模式
set +x
```

在执行脚本时,会显示添加了调试模式的代码段的执行情况,方便你跟踪代码的执行过程。

10. Bash 高级特性

  • 10.1 Shell 参数扩展
  • 10.2 数学运算
  • 10.3 特殊变量($?$$ 等)

Bash(GNU Bourne-Again SHell)是一个广泛使用的Unix shell和命令语言,具有许多高级特性,使其成为一种功能强大且灵活的工具。以下是一些Bash的高级特性:

  1. 通配符扩展(Wildcard Expansion):Bash允许使用通配符(如*?等)来匹配文件名和路径名,这使得对文件进行批量操作变得更加方便。

  2. 命令替换(Command Substitution):使用反引号()或$()语法,可以将命令的输出作为变量或参数传递给其他命令。

  3. 变量替换(Parameter Expansion):Bash支持各种变量替换操作,如${var}${var:-default}${var:=value}等,用于对变量进行操作和转换。

  4. 算术扩展(Arithmetic Expansion):通过双括号((( ... )))或$(( ... )),可以进行整数运算,并将结果赋给变量。

  5. 数组(Arrays):Bash支持一维数组,可以方便地存储和操作多个值。

  6. 关联数组(Associative Arrays):自Bash 4.0开始,还支持关联数组,允许使用字符串作为索引。

  7. 条件判断(Conditional Constructs):Bash提供了丰富的条件判断结构,如if语句、case语句、[[ ... ]]条件判断等,使得编写复杂的逻辑更加简洁。

  8. 循环结构(Loop Constructs):Bash支持forwhileuntil等循环结构,可用于重复执行代码块。

  9. 函数(Functions):可以在Bash中定义和调用函数,使代码模块化和重用更加方便。

  10. 作业控制(Job Control):Bash允许在后台运行命令,并可通过bgfg等命令控制作业的状态。

  11. 信号处理(Signal Handling):Bash可以捕获和处理各种信号,如SIGINTSIGTERM等,以及自定义信号。

  12. 文件描述符操作(File Descriptor Manipulation):可以在Bash中打开、关闭、重定向文件描述符,实现输入输出的灵活控制。

  13. 进程替换(Process Substitution):通过<(command)>(command),可以在不创建临时文件的情况下将命令的输出作为文件进行处理。

  14. 模式匹配(Pattern Matching):Bash支持在字符串操作中使用模式匹配,如字符串替换、匹配等。

这些高级特性使得Bash成为一种强大的脚本语言和交互式shell,可用于各种系统管理、自动化任务和编程工作。

当然,还有更多的高级特性,以下是一些其他的Bash高级特性:

  1. 参数扩展(Parameter Expansion):除了变量替换,Bash还支持参数扩展,如${parameter:offset:length}${parameter/pattern/string}等,用于对参数进行操作和转换。

  2. 引用(Quoting):Bash提供了多种引号机制,如单引号、双引号和反斜杠,用于对特殊字符进行转义或保留字面意义。

  3. Here Documents:通过<<和结束标记,可以在脚本中嵌入多行文本块。

  4. Here Strings:通过<<<,可以将字符串作为命令的输入。

  5. 函数参数处理:Bash函数支持位置参数,以及$@$*用于获取所有参数,$#用于获取参数数量。

  6. 进程控制:Bash支持对后台进程和作业进行控制,如jobskill等命令,以及trap用于捕获和处理信号。

  7. 调试功能:Bash提供了调试脚本的功能,如set -x启用跟踪模式、set -e在出现错误时退出等。

  8. 环境变量处理:Bash可以在脚本中设置和读取环境变量,以及通过export命令将变量导出给子进程。

  9. 输入/输出重定向:Bash支持将命令的输入和输出重定向到文件、管道或其他文件描述符。

  10. 进程控制:通过&将命令置于后台执行,以及nohupdisown等命令用于处理后台进程。

  11. Shell脚本编程风格:Bash脚本编程有许多最佳实践和约定,如代码缩进、注释、错误处理等,以提高脚本的可读性和可维护性。

这些高级特性使得Bash成为一种功能强大、灵活且易于使用的工具,适用于各种系统管理、自动化任务和编程工作。

第三部分:实际应用

11. 实际应用示例

11.1 系统管理任务

11.1.1 用户管理简单脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#!/bin/bash

# 用户管理脚本

declare -A users

# 添加新用户的方法
add_user() {
local username=$1
sudo useradd $username
users[$username]="$(id $username)"
}

# 删除用户的方法
delete_user() {
local username=$1
sudo userdel $username
unset users[$username]
}

# 修改用户密码的方法
change_password() {
local username=$1
sudo passwd $username
}

# 列出所有用户的方法
list_users() {
echo "当前系统上的用户列表:"
cut -d: -f1 /etc/passwd
}

# 显示用户详细信息的方法
user_details() {
local username=$1
echo "用户 $username 的详细信息:"
id $username
}

# 主函数
main() {
while true; do
echo "用户管理脚本"
echo "1. 添加用户"
echo "2. 删除用户"
echo "3. 修改用户密码"
echo "4. 显示用户列表"
echo "5. 显示用户详细信息"
echo "6. 退出"

read -p "请选择操作: " choice
case $choice in
1) read -p "请输入新用户的用户名: " username
add_user $username ;;
2) read -p "请输入要删除的用户名: " username
delete_user $username ;;
3) read -p "请输入要修改密码的用户名: " username
change_password $username ;;
4) list_users ;;
5) read -p "请输入要查看详细信息的用户名: " username
user_details $username ;;
6) echo "退出。"; break ;;
*) echo "无效的选择。" ;;
esac
done
}

# 执行主函数
main




  • 11.2 日志处理
  • 11.3 自动化任务

12. 脚本最佳实践

  • 12.1 代码风格和可读性
  • 12.2 脚本优化和性能
  • 12.3 安全性和防御性编程

13. 实用工具和框架

  • 13.1 使用 jq 处理 JSON 数据
  • 13.2 使用 curl 进行网络通信
  • 13.3 使用第三方库和工具

14. 远程脚本执行和管理

  • 14.1 使用 SSH 远程执行脚本
  • 14.2 使用 Ansible 等工具进行配置管理

15. 实战项目

  • 15.1 构建一个自动备份脚本
  • 15.2 简单的系统监控工具