shell
Linux Shell 是用户与 Linux 操作系统之间进行交互的命令行接口。 Shell 接受用户输入的命令并将其传递给操作系统内核进行执行。以下是对 Linux Shell 的一些基本介绍:
Shell 的种类
Section titled “Shell 的种类”在 Linux 系统中,有多种 Shell 可供选择,常见的有:
- Bash(Bourne Again Shell):最流行的 Linux Shell,几乎所有的 Linux 发行版默认都提供 Bash。
- Zsh(Z Shell):功能强大且灵活,具有许多用户友好的特性,如自动补全、语法高亮等。
- Fish(Friendly Interactive Shell):专注于用户体验,易于使用和配置。
- Dash:轻量级的 POSIX 兼容 Shell,常用于启动脚本中。
- 命令执行:用户可以输入各种命令,Shell 将其传递给操作系统执行。例如,
ls列出目录内容,cd改变当前目录。 - 脚本编写:Shell 脚本是一种编程语言,用户可以编写脚本来自动化任务。脚本通常以
.sh结尾。 - 重定向和管道:可以将命令的输出重定向到文件,或将一个命令的输出通过管道符
|传递给另一个命令。 例如,ls -l > output.txt将ls -l的结果输出到output.txt文件中。 - 环境变量:Shell 使用环境变量存储系统和用户信息。例如,
PATH环境变量包含可执行文件的搜索路径。
bash编程
Section titled “bash编程”Shebang (#!)
Section titled “Shebang (#!)”Shebang 是一个由 #! 开头的字符序列,后跟解释器的路径。它出现在脚本文件的第一行,
用于指定运行该脚本所需的解释器, 不仅用于shell脚本, python等脚本也可以使用。常见的 Shebang 形式有以下几种:
-
指定具体路径的解释器:
#!/bin/bash这里指定使用
/bin/bash解释器来运行脚本。 -
使用
env命令查找解释器:#!/usr/bin/env bash这种方式利用
env命令在PATH环境变量指定的路径中查找解释器。它比直接指定解释器路径更灵活,因为它不依赖解释器的固定路径。
#!/usr/bin/env bash 的优势
Section titled “#!/usr/bin/env bash 的优势”使用 #!/usr/bin/env bash 的好处在于提高了脚本的可移植性。不同的系统上 Bash 解释器的路径可能不同,而 env 命令可以根据环境变量自动找到正确的解释器路径。例如:
- 在一些系统上,Bash 可能位于
/bin/bash。 - 在另一些系统上,Bash 可能位于
/usr/local/bin/bash。
使用 #!/usr/bin/env bash 可以确保无论 Bash 安装在什么位置,只要它在 PATH 环境变量中,该脚本都可以正常运行。
shell脚本执行有多种方式
- ./cmd.sh 执行cmd.sh
- source cmd.sh 或 . ./source.sh
- bash cmd.sh(强制使用bash, 不会按照Shebang查找解释器)
#!/usr/bin/env bashecho "Hello world!"-
单行注释
Terminal window ## 这是注释内容# -
多行注释
Terminal window :<<EOFecho "多行注释"echo "多行注释"EOF
在 Bash 中,set 命令用于修改 shell 的行为和选项。通过使用不同的选项,set 命令可以改变 shell 的执行模式,从而帮助你更好地控制脚本的执行和调试。
常用选项
- -e, 当一个命令返回非零退出状态时,立即退出脚本。通常用于提高脚本的健壮性,确保在命令失败时脚本不会继续运行。
- -u, 在引用未定义变量时,立即退出脚本。可以帮助捕捉脚本中的拼写错误和未定义变量的使用。
- -x, 在执行每个命令之前,打印命令及其参数。常用于调试,帮助你了解脚本的执行过程。
- -o pipefail, 如果管道中的任意命令失败,则整个管道的返回值为非零。通常与 -e 选项结合使用,确保管道中的每个命令都成功。
-
- 选项, 你可以使用 + 选项来禁用某个选项。例如,set +e 将禁用 -e 选项。
## echo 输出, 默认会在结尾添加换行#
echo "普通字符串"# "中可以直接使用'echo "有引号的字符串' \""
# '字符串echo '"有双引号的字符串"'
# echo 输出变量name=shugecho "hello, ${name}"
# echo 输出换行echo -e "YES\nNo"
# 输出命令结果echo `pwd`echo $(pwd)## printf 打印, 不会自动在末尾添加换行#
printf '%d %s\n' 1 testprintf "%d %s\n" 1 test
printf %s abcecho "不换行"
printf "%s\n" "匹配" "没匹配"printf "%s %s %s\n" a b c d e f g
# 如果没有参数, %s用空字符串填充, %d用0代替printf "%s 和 %d \n"
printf "%-10s %-8s %-4s\n" 姓名 性别 体重printf "%-10s %-8s %-4.2f\n" 张三 男 66printf "%-10s %-8s %-4.2f\n" 李四 男 55.412312declare
Section titled “declare”-f 将操作或显示限制为函数名及函数定义。-F 只显示函数名(调试时附加行号和源文件)。-g 在shell函数中使用时创建全局变量;其他情况下忽略。-p 显示每个名称的属性和值。
*设置属性的选项:-a 创建数组(如果支持)。-A 创建关联数组(如果支持)。-i 增加整型属性。+i 删除整型属性。-l 增加小写属性,变量的值将转换为小写。+l 删除小写属性。-n 增加引用属性(如果该选项存在)。+n 删除引用属性(如果该选项存在)。-r 增加只读属性。-t 增加追踪属性。+t 删除追踪属性。-u 增加大写属性,变量的值将转换为大写。+u 删除大写属性。-x 增加导出属性。+x 删除导出属性。# 显示所有包含整型属性的变量和值。declare -p
# 定义变量b并赋值为5,具有整型属性。declare -i b=5
# 显示属性declare -p b
declare -A fruits=(['apple']='red' ['banana']='yellow')变量没有数据类型, 也不需要提前声明, 给变量赋值会直接创建变量
变量命名规则
- 只能使用英文字母, 数字和下划线, 不能以数字开头
- 中间不能有空格
- 不能使用关键字
- 给变量赋值时,=两边不能有空格
name=shugecho $nameecho ${name}
# 只读变量readonly name#name=new #取消注释将报错
# 删除变量new=newunset newecho ${new}变量类型
- 局部变量 - 仅在脚步范围内有效(使用local声明的局部变量, 作用域仅限于函数内部, local只能在函数内部使用)
- 环境变量 - 在当前shell回话内所有程序和脚本都有效, 使用export创建
| 常见环境变量 | 描述 |
| --------------------- | ----------------------------------------------------------------------------------------- |
| HOME | 当前用户的家目录 |
| PATH | 用分号分割的目录列表, shell在执行外部命令时,会依次到这些目录里面查询 |
| PWD | 当前工作目录 |
| RANDOM | 0-32767之间的整数 |
| UID | 数值类型, 当前用户的用户ID |
| PS1 | 主要系统输入提示符 |
| PS2 | 次要系统输入提示符 |
| $0 | 当前脚本或命令的名称 |
| $1, $2, ..., $N | 传递给脚本或函数的参数,$1 是第一个参数,$2 是第二个参数,依此类推 |
| $# | 传递给脚本或函数的参数个数 |
| $@ | 传递给脚本或函数的所有参数(分开显示) |
| $* | 传递给脚本或函数的所有参数(作为单个字符串) |
| $? | 前一个命令的退出状态,成功返回 0,失败返回非零值 |
| $$ | 当前脚本或命令的进程ID |
| $! | 最后一个后台命令的进程ID |
| $_ | 最后执行的命令的最后一个参数,或者在交互模式下表示最后一个命令 |
| $- | 当前 Shell 的启动参数 |
| ${varname:-word} | 如果变量varname存在且不为空,则返回它的值,否则返回word |
| ${varname:=word} | 如果变量varname存在且不为空,则返回它的值,否则将它设为word,并且返回word |
| ${varname:+word} | 如果变量名存在且不为空,则返回word,否则返回空值。 |
| ${varname:?message} | 如果变量varname存在且不为空,则返回它的值,否则打印出varname: message,并中断脚本的执行。 |
shell字符串可以使用单引号, 双引号, 也可以不加引号
# - 双引号# 字符串识别变量echo "单双引号, ${PATH}"# - 单引号# 不识别变量, 字符串内不能出现单独的单引号, 但可以成对出现echo 'hello, 'world''# hello, world
echo "字符串拼接"name=shug
h1='hello, '${name}''h2='hello, ${name}'
echo ${h1} ${h2}
h1="hello, "${name}""h2="hello, ${name}"echo ${h1} ${h2}
echo "获取字符串长度"name=shugecho "name的值是: ${name}, 长度是: ${#name}"
echo "截取字符串"# 从第二个字符开始截取后面的字符串echo "${name:1}"# 从第二个字符开始截取后面一个字符echo "${name:1:1}"
echo "使用expr命令查找子字符串"echo `expr index "${name}" h`# 创建数组nums=([2]=2 [0]=0 [1]=1)gender=(male female)
# 访问单个元素echo ${nums[1]}echo ${gender[1]}
# 访问所有元素#
echo ${nums[*]}echo ${gender[@]}
# * 和@ 的区别#nums=("one" "twe" "one one")
printf " %s\n" ${nums[*]}echo "---------------"printf " %s\n" ${nums[@]}
echo "+++++++++++++++"printf " %s\n" "${nums[*]}"echo "---------------"printf " %s\n" "${nums[@]}"
echo "数组切片"# 从0开始取两个元素echo ${nums[@]:0:2}
# 数组长度echo ${#nums[*]}
# 向数组添加元素nums=("zero" "${nums[@]}")
echo ${nums[@]}
# 删除元素unset nums[0]echo ${nums[@]}
# 定义关联数组。declare -A fruits=(['apple']='red' ['banana']='yellow')
## 遍历数组(普通数组和关联数组都可以使用)for fruit in "${!fruits[@]}"; do echo "$fruit=${fruits[$fruit]}"done-
算数运算符
| 运算符 | 说明 | 举例 | | :----- | :--- | :------------ | | + | 加 | expr
y | | - | 减 | expr y | | * | 乘 | expr y | | / | 除 | expr y | | % | 取余 | expr y | | = | 赋值 | x = x == x != $y ] | -
关系运算符
| 运算符 | 说明 | 举例 | | :----- | :-------------- | :------------ | | -eq | 等 | [
y ] | | -ne | 不等 | [ y ] | | -gt | 大于 | [ y ] | | -lt | 小于 | [ y ] | | -ge | 大于等于 | [ y ] | | -le | 小于等于 | [ y ] | | = | 字符串相等 | [[ b]] | | != | 字符串不相等 | [[ b]] | | -z | 字符串长度为0 | [[-z a]] | | | 字符串为空 | [[$a]] | -
布尔运算符
| 运算符 | 说明 | 举例 | | :----- | :--- | :----------------------- | | ! | 非 | [ !false ] | | -o | 或 | [
y -eq 2 ] | | -a | 与 | [ y -eq 2 ] | -
逻辑运算符
| 运算符 | 说明 | 举例 | | :----- | :--- | :----------------------------- | | && | 与 | [[
y -gt 100]] | | || | 或 | [[ y -gt 100]] | -
文件测试运算符
| 运算符 | 说明 | 举例 | | :----- | :--------------------- | :----------- | | -b | 块设备文件 | [[-b
file]] | | -d | 目录 | [[-d file]] | | -g | 文件设置SGID | [[-g file]] | | -p | 管道文件 | [[-p file]] | | -r | 文件可读 | [[-r file]] | | -x | 块文件可执行 | [[-x file]] | | -e | 文件或目录存在 | [[-e $file]] |
if [[ 1 -eq 1 ]];then echo "1 -eq 1"else echo "1 -ne 1"fia=2
if [[ $a -eq 1 ]];then echo "a == 1"elif [[ $a -eq 2 ]];then echo "a == 2"else echo "else ${a}"ficase ${a} in 0) echo "a=0" ;; 1) echo "a=1" ;; 2) echo "a=2" ;; 3) echo "a=3" ;; *) echo "a=${a}" ;;esacfor arg in 1 2 3do echo ${arg}donefor arg in {1..3};do echo ${arg}donefor (( i=0; i<3; i++ ));do echo ${i}donefor file in ./*.sh;do echo "file: ${file}"donecounter=1until [ $counter -gt 5 ]; do echo "Counter: $counter" ((counter++))donewhile [[ ${a} -lt 4 ]];do echo "a=${a}" a=$(( a+1 ))doneselect i in 1 2 3 4 5 6;do echo $i if [[ $i -eq 2 ]]; then break; fidonecalc() { echo "call calc" echo "\$*: $*" echo "\$@: $@" echo "\$\#: $#" echo "\$0: $0" echo "\$FUNCNAME: $FUNCNAME"}
calcecho "res: $?"echo "pid: $!"echo "set: $-"在 Bash 脚本中,可以使用多种方式读取输入,包括 read 命令、命令行参数、重定向等。以下是几种常用方法的示例:
使用 read 命令读取用户输入
Section titled “使用 read 命令读取用户输入”#!/bin/bash
echo "请输入你的名字:"read nameecho "你好, $name!"从命令行参数读取输入
Section titled “从命令行参数读取输入”#!/bin/bash
if [ $# -lt 1 ]; then echo "请提供一个参数" exit 1fi
name=$1echo "你好, $name!"使用 read 命令从文件读取输入
Section titled “使用 read 命令从文件读取输入”假设有一个文件 input.txt,内容如下:
AliceBobCharlie读取文件内容并逐行处理:
#!/bin/bash
while IFS= read -r linedo echo "你好, $line!"done < input.txt使用重定向读取输入
Section titled “使用重定向读取输入”可以使用输入重定向将文件内容传递给脚本:
#!/bin/bash
while IFS= read -r linedo echo "你好, $line!"done运行脚本时将文件内容重定向到脚本:
./script.sh < input.txt从命令管道读取输入
Section titled “从命令管道读取输入”通过管道将命令的输出传递给脚本:
#!/bin/bash
while IFS= read -r linedo echo "你好, $line!"done使用管道运行脚本:
echo -e "Alice\nBob\nCharlie" | ./script.sh