Skip to content

03 万丈高楼平地起 - Bash 的基础语法 -2

bash 循环

for 循环

  • for 循环是一种常用的循环结构,用于遍历一个列表或范围,并对每个元素执行一组命令。
bash
for variable in list; do
    command1
    command2
    ...
done
  • variable:循环变量,用于存储当前遍历的元素。
  • list:要遍历的列表或范围。
  • command1command2 等:在每次循环中要执行的命令。
用法类型示例代码说明
遍历列表for item in item1 item2 item3; do echo $item; done依次遍历 item1item2item3
遍历文件for file in *.txt; do echo $file; done遍历当前目录下所有 .txt 文件。
C 风格的 for 循环for ((i=0; i<5; i++)); do echo $i; done使用 C 风格语法,i 从 0 遍历到 4。
使用 seq 生成序列for i in $(seq 1 5); do echo $i; done使用 seq 生成 15 的数字序列。
遍历数组for fruit in "${array[@]}"; do echo $fruit; done遍历数组 array 中的每个元素。
使用命令替换的循环for filename in $(ls *.sh); do echo $filename; done使用 $(ls *.sh) 获取所有 .sh 文件并遍历。

遍历数组列表

bash
  0902 git:(main) cat for_various_lists.sh
#!/bin/bash

# 遍历字符串列表
for item in one two three; do
    echo "Item: $item"
done

# 遍历数字列表
for VARIABLE in 1 2 3 4 5; do
  echo "Number: $VARIABLE"
done

# 定义一个数组
fruits=("Apple" "Banana" "Cherry" "Date" "Elderberry")
# 遍历数组
for fruit in "${fruits[@]}"
do
    echo "Fruit: $fruit"
done

# 定义一个字符串列表
colors="Red Green Blue Yellow"
# 遍历字符串列表
for color in $colors
do
    echo "Color: $color"
done

  0902 git:(main) bash for_various_lists.sh
Item: one
Item: two
Item: three
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
Fruit: Apple
Fruit: Banana
Fruit: Cherry
Fruit: Date
Fruit: Elderberry
Color: Red
Color: Green
Color: Blue
Color: Yellow

遍历文件

bash
  0902 git:(main) cat for_files.sh
#!/bin/bash

# 创建 5 个测试文件
touch file[1-5].txt

# 遍历当前目录下的所有 .txt 文件
for file in *.txt; do
  echo "Processing $file file..."
done

# 删除所有 .txt 文件
rm -f *.txt

# 遍历 ls 命令的输出,列出所有 .sh 文件
for file in $(ls *.sh); do
  echo "Found file: $file"
done

  0902 git:(main)  bash for_files.sh
Processing file1.txt file...
Processing file2.txt file...
Processing file3.txt file...
Processing file4.txt file...
Processing file5.txt file...
Found file: for_c_style.sh
Found file: for_files.sh
Found file: for_infinite.sh
Found file: for_nested.sh
Found file: for_ranges.sh
Found file: for_seq.sh
Found file: for_various_lists.sh

遍历数字或字母范围

bash
  0902 git:(main) cat for_ranges.sh
#!/bin/bash

# 遍历 1 到 5 的数字
for i in {1..5}; do
    echo "Number: $i"
done

# 遍历字母 a 到 c
for letter in {a..c}; do
    echo "Letter: $letter"
done

  0902 git:(main) bash for_ranges.sh
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
Letter: a
Letter: b
Letter: c

使用 C 风格的 for 循环

bash
  0902 git:(main) cat for_c_style.sh
#!/bin/bash

# 使用 C 风格的 for 循环
for ((i = 0; i < 5; i++)); do
  echo "Index: $i"
done

  0902 git:(main) bash for_c_style.sh
Index: 0
Index: 1
Index: 2
Index: 3
Index: 4

使用 seq 生成序列

bash
  0902 git:(main) cat for_seq.sh
#!/bin/bash

# 使用 seq 生成 1 到 5 的数字序列
for i in $(seq 1 5); do
  echo "Number: $i"
done

# 遍历 1 到 10 的数字,步长为 2
for i in $(seq 1 2 10); do
  echo "Number: $i"
done

  0902 git:(main) bash for_seq.sh
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
Number: 1
Number: 3
Number: 5
Number: 7
Number: 9

嵌套循环

bash
  0902 git:(main) cat for_nested.sh
#!/bin/bash

# 外层循环
for i in {1..3}; do
    # 内层循环
    for j in {A..C}; do
        echo "Outer loop: $i, Inner loop: $j"
    done
done

  0902 git:(main) bash for_nested.sh
Outer loop: 1, Inner loop: A
Outer loop: 1, Inner loop: B
Outer loop: 1, Inner loop: C
Outer loop: 2, Inner loop: A
Outer loop: 2, Inner loop: B
Outer loop: 2, Inner loop: C
Outer loop: 3, Inner loop: A
Outer loop: 3, Inner loop: B
Outer loop: 3, Inner loop: C

for 实现无限循环

bash
  0902 git:(main) cat for_infinite.sh
#!/bin/bash

# 无限循环
for (( ; ; )); do
    echo "This is an infinite loop!"
    # 为避免陷入死循环,可以加入 `sleep` 让程序休眠
    sleep 1
done

  0902 git:(main) bash for_infinite.sh
This is an infinite loop!
This is an infinite loop!
This is an infinite loop!
This is an infinite loop!
^C

while 循环

  • while 循环用于在给定的条件为真时反复执行一组命令。
bash
while [ condition ]; do
    # commands
done
  • condition:条件表达式,条件为真时,循环继续执行。当条件变为假时,循环结束。
  • do:开始执行循环体的命令。
  • done:结束循环体。

数值比较

  • 使用 [ ](( )) 进行数值比较。
  • 常用的数值比较运算符:
符号描述
-lt (less than)小于
-le (less or equal)小于等于
-eq (equal)等于
-ne (not equal)不等于
-gt (greater than)大于
-ge (greater or equal)大于等于
bash
  0902 git:(main) cat while_numeric_comparison_loops.sh
#!/bin/bash

counter=1
while [ $counter -le 5 ]; do # 当 counter 小于等于 5 时继续循环
    echo "Counter: $counter"
    ((counter++)) # 计数器加 1
done

number=3
while [ $number -gt 0 ]; do # 当 number 大于 0 时继续循环
    echo "Number: $number"
    ((number--))
done

index=0
while ((index < 5)); do # 当 index 小于 5 时继续循环
    echo "Index: $index"
    ((index++))
done
  0902 git:(main) bash while_numeric_comparison_loops.sh
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
Number: 3
Number: 2
Number: 1
Index: 0
Index: 1
Index: 2
Index: 3
Index: 4

字符串比较

  • 使用 [ ] 进行字符串比较。
  • 常用的字符串比较运算符:
符号描述
=等于
!=不等于
-z字符串为空
-n字符串不为空
bash
  0902 git:(main) cat while_string_comparison_loops.sh
#!/bin/bash

input="yes"
while [ "$input" = "yes" ]; do # 当输入是 "yes" 时继续循环
    echo "Do you want to continue? (yes/no)"
    read input
done

string="hello"
while [ "$string" != "bye" ]; do # 当 string 不等于 "bye" 时循环
    echo "String is: $string"
    string="bye" # 改变条件,以避免无限循环
done
  0902 git:(main) bash while_string_comparison_loops.sh
Do you want to continue? (yes/no)
yes
Do you want to continue? (yes/no)
no
String is: hello

文件状态

选项描述
-e (exists)文件存在
-f (file)文件存在且是普通文件
-d (directory)文件存在且是目录
-r (readable)文件可读
-w (writable)文件可写
-x (executable)文件可执行
bash
  0902 git:(main) cat while_file_status_check.sh
#!/bin/bash

filename="test.txt"
index=0
while [[ ! -e $filename && $index -le 3 ]]; do
    echo "Waiting for file $filename to be created..."
    sleep 2 # 等待 2 秒后再检查
    ((index++))
done

# 如果 index 大于 3,则说明文件不存在
if [ $index -ge 3 ]; then
    echo "File $filename not found after 6 seconds."
fi

echo "hello data.txt" >data.txt
while [ -f 'data.txt' ]; do
    echo "File $filename exists."
    cat 'data.txt'
    break # 避免无限循环,使用 break 跳出循环
done

rm -f data.txt
  0902 git:(main) bash while_file_status_check.sh
Waiting for file test.txt to be created...
Waiting for file test.txt to be created...
Waiting for file test.txt to be created...
Waiting for file test.txt to be created...
File test.txt not found after 6 seconds.
File test.txt exists.
hello data.txt

逻辑运算符

  • &&:逻辑与(and)
  • ||:逻辑或(or)
  • !:逻辑非(not)
bash
  0902 git:(main) cat while_logical_operators_loops.sh
#!/bin/bash

# 逻辑与:当 count 小于等于 3 且文件存在时循环
count=1
if [ ! -f "data.txt" ]; then # 如果 data.txt 不存在,则创建它
    touch data.txt
fi

# 使用 [[ ]] 进行条件判断
while [[ $count -le 3 && -f "data.txt" ]]; do
    echo "Count: $count"
    ((count++))
done
rm -f data.txt

# 逻辑或:当 index 大于 12 或不等于 15 时循环
index=18
while [[ $count -gt 12 || $index -ne 15 ]]; do
    echo "Index: $index"
    ((index--))
done

# 逻辑非:当 num 不等于 4 时循环
num=6
while [ ! $num -eq 4 ]; do
    echo "Num: $num"
    ((num--))
done
  0902 git:(main) bash while_logical_operators_loops.sh
Count: 1
Count: 2
Count: 3
Index: 18
Index: 17
Index: 16
Num: 6
Num: 5

无限循环 true

Bash 还可以使用 truefalse 控制循环:

  • true:始终返回 true,形成无限循环。
  • false:始终返回 false,不执行循环。
bash
  0902 git:(main) cat while_infinite_loop_with_true.sh
#!/bin/bash

# 使用 true 实现无限循环
while true; do # 无限循环,除非使用 break 或 Ctrl+C 停止
    echo "This is an infinite loop! Press [CTRL+C] to stop."
    sleep 1
done
  0902 git:(main) bash while_infinite_loop_with_true.sh
This is an infinite loop! Press [CTRL+C] to stop.
This is an infinite loop! Press [CTRL+C] to stop.
This is an infinite loop! Press [CTRL+C] to stop.
This is an infinite loop! Press [CTRL+C] to stop.
^C

使用命令作为条件

while 循环条件还可以是一个命令或命令的组合。如果命令执行成功(返回状态码为 0),条件为真,循环继续执行。

bash
  0902 git:(main) cat while_command_output_condition_loop.sh
#!/bin/bash

# ping baidu.com 成功时返回 0,失败时返回非 0 值
while ping -c 1 www.baidu.com >/dev/null; do # 只要能 ping 通 baidu.com,继续循环
    echo "Baidu is reachable"
    sleep 2
    echo "192.0.2.1 www.baidu.com" | sudo tee -a /etc/hosts
done
echo "Baidu is not reachable anymore"
sudo sed -i '/192.0.2.1 www.baidu.com/d' /etc/hosts

# 使用命令的退出状态码作为条件
if [ -f "data.txt" ]; then
    rm -f data.txt
fi
echo "hello data.txt" >data.txt
while grep -q "hello" data.txt; do
    echo "Found hello in data.txt. data.txt contents:"
    cat data.txt
    break
done
rm -f data.txt
  0902 git:(main) bash while_command_output_condition_loop.sh
Baidu is reachable
192.0.2.1 www.baidu.com
Baidu is not reachable anymore
Found hello in data.txt. data.txt contents:
hello data.txt

读取文件内容

bash
  0902 git:(main) cat while_file_reading_loops.sh
#!/bin/bash

if [ -f "data.txt" ]; then
    rm -f data.txt
fi

# sudo dnf install -y epel-release
# sudo dnf install -y pwgen
# 生成一个包含 5 行 16 个随机字符的文件
pwgen 16 5 >data.txt

# 使用 while 循环读取文件内容
while read -r line; do
    echo "Line: $line"
done <data.txt

rm -f data.txt
  0902 git:(main) bash while_file_reading_loops.sh
Line: saeRae8cah5aezei
Line: aidooXoov3Kooyie
Line: queeshee4OocoYi7
Line: aeveezeo4IephaiX
Line: ahK3Quie1fuaRaa2

break 和 continue

  • break:用于立即退出循环。
  • continue:用于跳过当前循环的剩余部分,直接进入下一次循环。
bash
  0902 git:(main) cat while_nested_loops_break_continue.sh
#!/bin/bash

# while 实现双层循环
# 外层循环
outer_index=0
while [ $outer_index -le 6 ]; do
    # 内层循环
    inner_index=0
    while [ $inner_index -lt 3 ]; do

        # 内层循环使用 continue 跳过当前循环
        if [ $inner_index -eq 1 ]; then
            ((inner_index++)) # 在跳过之前递增 inner_index
            continue
        fi

        echo "Outer loop: $outer_index, Inner loop: $inner_index"
        ((inner_index++))
    done

    # 外层循环使用 break 退出循环
    if [ $outer_index -eq 4 ]; then
        break
    fi
    ((outer_index += 2))
done
  0902 git:(main) bash while_nested_loops_break_continue.sh
Outer loop: 0, Inner loop: 0
Outer loop: 0, Inner loop: 2
Outer loop: 2, Inner loop: 0
Outer loop: 2, Inner loop: 2
Outer loop: 4, Inner loop: 0
Outer loop: 4, Inner loop: 2

until 循环

  • until 循环是一种条件控制结构,它重复执行一系列命令,直到给定的条件为真时停止。
  • until 循环与 while 循环的主要区别在于条件的真伪:while 循环在条件为真时运行,而 until 循环在条件为假时运行。
bash
until [ condition ]; do
  commands
done
  • condition 返回的退出状态码不是 0(即条件为假),则循环体内的 commands 将被执行。
  • 一旦 condition 测试为真(返回退出状态码 0),循环就会停止。

简单的计数循环

bash
  0902 git:(main) cat until_counter_loop.sh
#!/bin/bash

counter=0
until [ $counter -gt 5 ]; do # 循环将在 counter 大于 5 时停止
    echo "Counter: $counter"
    ((counter++))
done
  0902 git:(main) bash until_counter_loop.sh
Counter: 0
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5

读取用户输入直到符合条件

bash
  0902 git:(main) cat until_user_input.sh
#!/bin/bash

input=""
until [ "$input" == "exit" ]; do # 循环将在输入为 "exit" 时停止
    read -p "Enter something (type 'exit' to quit): " input
    echo "You entered: $input"
done

answer=""
until [[ "$answer" == "yes" || "$answer" == "no" ]]; do # 循环将在输入为 "yes" 或 "no" 时停止
    read -p "Please enter 'yes' or 'no': " answer
    echo "You entered: $answer"
done
  0902 git:(main) bash until_user_input.sh
Enter something (type 'exit' to quit): ex
You entered: ex
Enter something (type 'exit' to quit): exitt
You entered: exitt
Enter something (type 'exit' to quit): exit
You entered: exit
Please enter 'yes' or 'no': noooo
You entered: noooo
Please enter 'yes' or 'no': ye
You entered: ye
Please enter 'yes' or 'no': yes
You entered: yes

使用 until 循环等待文件存在

bash
  0902 git:(main) cat until_file_exists.sh
#!/bin/bash

filename="data.txt"
index=0
until [ -e "$filename" ]; do # 循环将在文件存在时停止
    echo "File $filename does not exist. Waiting..."
    sleep 1
    if [ $index -ge 3 ]; then
        touch $filename
    fi
    ((index++))
done

echo "File $filename exists!"
rm -f $filename
  0902 git:(main) bash until_file_exists.sh
File data.txt does not exist. Waiting...
File data.txt does not exist. Waiting...
File data.txt does not exist. Waiting...
File data.txt does not exist. Waiting...
File data.txt exists!

检查网络连接是否恢复

bash
  0902 git:(main) cat until_network_check.sh
#!/bin/bash

# ping baidu.com 成功时返回 0,失败时返回非 0 值
until ping -c 1 baidu.com >/dev/null; do # 只要不能 ping 通 baidu.com,继续循环
    echo "Baidu is not reachable"
    sleep 2
done
echo "Baidu is reachable"
  0902 git:(main) bash until_network_check.sh
Baidu is not reachable
Baidu is reachable

环境变量

常见环境变量

变量名描述示例值
PATH决定了 shell 在执行命令时搜索的目录列表/usr/local/sbin:/usr/local/bin:/usr/bin
HOME当前用户的主目录路径/root
USER当前用户名root
SHELL当前 shell 程序的路径/bin/zsh
PWD当前工作目录/home/wwvl/VSCodeProject
LANG系统语言和地区设定,用于处理不同语言的输出和格式en_US.UTF-8
EDITOR默认的文本编辑器nvim
HOSTNAME当前机器的主机名VM-16-3-centos
TERM终端类型xterm
HISTFILE存储命令历史的文件路径/home/wwvl/.bash_history
HISTSIZE命令历史记录的最大条数50000

查看环境变量

bash
printenv
# 或
env

设置环境变量

bash
# 使用 export 命令设置环境变量
export VAR_NAME="value"

# 设置 MY_VAR 变量
export MY_VAR="Hello, World!"

在命令前临时设置环境变量,变量仅对当前命令有效。

bash
# 临时设置环境变量
VAR_NAME="value" command_to_run

# 设置 LANG 变量运行 ls 命令
# ls 命令只在当前命令行中使用 fr_FR.UTF-8 环境
LANG=fr_FR.UTF-8 ls # 临时设置环境变量

删除环境变量

bash
# 使用 unset 命令删除环境变量
unset VAR_NAME

# 删除 MY_VAR 变量
unset MY_VAR

持久化环境变量

为了使环境变量在 shell 会话之间持久存在,可以将其添加到用户的 shell 配置文件中(如 .bashrc(.zshrc) 或 .bash_profile),这样它们会在每次启动 shell 时自动加载。

bash
# 使用 nvim 编辑 .bashrc/.zshrc/.bash_profile 文件
nvim ~/.bashrc
# 在文件中添加环境变量
export MY_VAR="Persistent Value"
# 保存文件并退出
:wq

# 或者使用 echo 命令将环境变量添加到配置文件中
echo "export MY_VAR='Persistent Value'" >> ~/.bashrc
echo "export MY_VAR='Persistent Value'" | tee -a ~/.bashrc

# 使配置文件生效
source ~/.bashrc

环境变量与 Shell 变量的区别

  • 环境变量: 通过 export 命令设置的变量,它们可以被当前 shell 及其子进程访问。
  • Shell 变量: 没有被 export 的变量,它们仅在当前 shell 会话中有效。

环境变量的应用场景

  1. 配置 PATH: 增加自定义命令或工具的路径。

    bash
    export PATH=$PATH:/custom/path
  2. 配置代理服务器: 设置 http_proxyhttps_proxy 来配置网络代理。

    bash
    export http_proxy="http://127.0.0.1:7890"
    export https_proxy="http://127.0.0.1:7890"
  3. 配置编译器: 设置 CCCXX 环境变量来指定 C 和 C++ 编译器。

    bash
    export CC=gcc
    export CXX=g++

bash 的执行方式

执行方式描述使用示例适用场景
直接指定 Shell 解释器添加 shebang 并赋予执行权限后,直接执行脚本。./script.sh执行独立的、已配置好权限的脚本。
指定 Shell 解释器执行不需要 shebang 或执行权限,通过解释器名称(如 bash)运行脚本。bash script.sh测试或运行临时脚本。
使用 source. 命令执行在当前 Shell 环境中执行脚本,不创建子 Shell。脚本中的变量和函数在当前环境中生效。source script.sh. script.sh加载配置文件或设置环境变量。
使用 exec 命令执行将当前 Shell 进程替换为脚本进程,不会创建新的子 Shell。exec ./script.sh替换当前进程,执行完毕后退出。

直接指定 Shell 解释器

通过在脚本顶部添加 shebang(通常是 #!/bin/bash#!/bin/sh)指定脚本解释器,然后为脚本文件添加执行权限,最后直接执行脚本。

bash
# 创建脚本文件,并在文件开头添加 shebang
$ nvim script.sh
#!/bin/bash
echo "Hello, World!"

# 保存文件并退出
:wq

# 为脚本添加可执行权限
chmod +x script.sh

# 直接执行脚本
./script.sh

说明

  • #!/bin/bash 告诉系统使用 /bin/bash 作为解释器。
  • 需要 chmod +x 来为脚本文件添加执行权限。
  • 使用 ./script.sh 或者指定完整路径 /path/to/script.sh 来运行脚本。

指定 Shell 解释器执行

不需要给脚本添加执行权限,直接使用解释器名称(如 bashsh)来执行脚本文件。

bash
# 创建脚本文件,在文件中添加内容
nvim script.sh
echo "Hello, World!"

# 保存文件并退出
:wq

# 指定 bash 解释器执行
bash script.sh
# 指定 sh 解释器执行
sh script.sh

说明

  • bashsh 会直接调用脚本进行解释执行。
  • 适合测试或运行临时脚本,不需要为脚本添加执行权限。

使用 source. 命令执行

使用 source. 命令执行脚本,脚本中的所有命令会在当前 Shell 环境中执行,而不会创建子 Shell 进程。

bash
# 创建脚本文件,并在文件中添加内容
nvim script.sh
echo "Hello, World!"

# 保存文件并退出
:wq

# 使用 source 命令执行
source script.sh
# 使用 . 命令执行
. script.sh

说明

  • 使用 source. 命令执行时,所有在脚本中定义的变量或执行的命令都在当前 Shell 环境中生效,而不会影响到外部 Shell 环境(即,变量和函数定义将持续存在)。
  • 如果使用 ./script.shbash script.sh 来运行,上述脚本中定义的 MY_VAR 变量不会在当前 Shell 中生效,因为它会在子 Shell 中执行。

使用 exec 命令执行

使用 exec 命令可以将当前 Shell 替换为执行的脚本进程,从而不会创建新的子进程。

bash
# 创建脚本文件,并在文件中添加内容
nvim script.sh
echo "This is executed by exec."

# 保存文件并退出
:wq

# 使用 exec 命令执行
exec ./script.sh

说明

  • 执行 exec ./script.sh 后,当前 Shell 进程被替换为 script.sh,脚本执行完毕后不会返回到原来的 Shell,会直接退出。
  • 常用于终止当前 Shell 并切换到一个新进程执行。