bash@特殊字符@环境变量符号@特殊参数@参数扩展和替换@字符串处理用法总结

文章目录

特殊字符(元字符)

Bash 会对某些字符赋予非字面意义。这些字符执行特殊指令或具有其他含义;它们被称为"特殊字符"或"元字符"。

以下是一些比较常见的特殊字符用法:(部分被淘汰的字符未被列出,单独一节说明,例如反引号对被$()写法所取代)

字符 描述
blank 空白字符 ------包括6种:制表符、换行符、垂直制表符、换页符、回车符或空格 ,最常见的是空格。Bash 使用空白字符来确定单词的起始和结束位置。第一个单词是命令名称,其他单词则作为该命令的参数。
$ 扩展 ------用于各种类型的扩展: 参数扩展(例如 $var${var} )、命令替换(例如 $(command) )或算术扩展(例如 $((expression)) )。
'' 单引号 ------用于保护其内部的文本,使其具有字面含义 。使用单引号时,Bash 的任何解释通常都会被忽略:特殊字符会被忽略,多个单词也不会被拆分。
"" 双引号 ------保护其内部的文本不被拆分成多个单词或参数 ,但允许进行替换 ; 字符$在双引号内保留其特殊含义(参见 Shell 展开). 除此之外,通常会**阻止bash解释大多数其他特殊字符的含义** 。(bash明确规定了只有若干个字符可在双引号中通过反斜杠`\`被解释为转义用途) 也就是说,将字符放在双引号中会保留引号内所有字符的字面值,除了 `$`,,\ ,如果启用了history,则还包括!,如果启用posix模式,则情况有不一样,即使启用了历史扩展,' ! '在双引号内也没有特殊含义。
\ 未加引号的反斜杠" \ "是 Bash 转义字符。转义符(反斜杠)可以防止下一个字符被解释为特殊字符。这在引号内、双引号外都有效,但在单引号内通常会被忽略。
# 注释 ------ # 字符开始一段注释,该注释会延伸到行尾。注释是解释性的说明,不会被 shell 处理。
= 赋值 ------给变量赋值(例如 logdir=/var/log/myprog )。 = 字符两侧不允许有空格。
[[ ]] 测试 ------对条件表达式进行求值,以确定其结果为"真"还是"假"。在 Bash 中,测试用于比较字符串、检查文件是否存在等等。
! 否定 --- 用于否定或撤销测试或退出状态。例如: ! grep text file; exit $?
>, >>, <,<<,<<< 重定向 ------将命令的输出输入 重定向到文件。(通常是修改stdin,stdout的指向,>,>>用于输出重定向到文件,<用于输入重定向到文件);此外还有<<,<<<,分别将输入重定向到here-doc,here-string
` `
; 命令分隔符 ------用于分隔同一行上的多个命令。
{ } 内联组 ------花括号内的命令会被视为一个命令。当 Bash 语法只需要一个命令,而不需要使用函数时,使用内联组非常方便。
( ) 子 shell 组 ------与上述类似,但其中的命令会在子 shell (一个新进程)中执行。它的用法很像沙箱,如果某个命令产生了副作用(例如更改变量),则不会影响当前 shell。
(( )) 算术表达式 ------在算术表达式中,诸如 +-*/ 之类的字符是用于计算的数学运算符。它们可以用于变量赋值(如 (( a = 1 + 4 )) )以及测试(如 if (( a < b )) )。
$(( )) 算术展开 ------与上述类似,但表达式被替换为其算术计算的结果。例如: echo "The average is $(( (a+b)/2 ))"
*, ? 通配符 ------匹配文件名部分内容的"通配符"(例如 ls *.txt )。
~ 用户主目录 ------波浪号表示用户主目录(波浪号扩展 ,和参数扩展不同,在引号中,例如"~/demo"路径和"$HOME/demo"是不同的,对于带有空格或者需要扩展变量的情况下,建议使用后者$HOME表示家目录)。单独使用或后跟 / 时,表示当前用户的主目录;否则,必须指定用户名(例如 ls ~/Documents; cp ~john/.bashrc . )。
& 后台运行 ------当在命令末尾使用时,在后台运行该命令(不要等待其完成)。
$'...' Bash 支持 $'...' 引号语法(ANSI-C Quoting ),它会展开单引号之间文本中的 ANSI-C 反斜杠转义字符,形如 $' string ' 的字符序列被视为一种特殊的单引号。该序列会展开为 string ,其中 string 中反斜杠转义的字符会按照 ANSI C 标准的规定进行替换。
$"..." Bash 支持 $"..." 引号语法,并根据语言环境对双引号之间的字符进行翻译。此类型字符串用于创建国际化脚本,实际使用较少,使用英文基本实现国际化要求

重点摘要

转义\

转义 --- 反斜杠(\)可阻止其后的字符被解释为特殊字符。此功能在引号之外(不使用引号的时候)、双引号内部有效,而在单引号内通常会被忽略。

它会保留其后下一个字符的字面值,移除该字符本身的任何特殊含义

newline 除外。如果出现一 \newline(即斜杠后面跟着一个回车符) ,且反斜杠本身未加引号, \newline 会被视为行继续符 (并不换行)(即,它会从输入流中移除并被忽略 )可以用来输入多行内容,最终当做只有一行处理。

典型的例子是curl这种命令行工具,其参数可能很多,往往换行输入提高可读性,这就常用到\newline


✅ 1. 在引号之外(Outside of quoting)

反斜杠可以转义特殊字符(如空格、$*> 等),使其失去特殊含义。

bash 复制代码
# 例子:转义空格,让文件名中的空格不被当作分隔符
touch my\ file.txt     # 创建一个名为 "my file.txt" 的文件(含空格)

# 例子:转义通配符
echo \*                # 输出字面量 "*",而不是当前目录下的所有文件
echo *                 # 输出当前目录下所有文件(通配符展开)

\newline的例子

bash 复制代码
# 例子:转义换行(用于命令换行)
echo "Hello, \
World"
# 输出:Hello, World(反斜杠后接换行符会被忽略)

✅ 2. 在双引号内部(Inside double quotes)

在双引号中,反斜杠 仅对特定字符有效$、`````、"\ 和换行符(\n)。

bash 复制代码
# 转义 $,防止变量展开
name="Alice"
echo "\$name is $name"        # 输出:$name is Alice

# 转义双引号本身
echo "He said \"Hello\""     # 输出:He said "Hello"

# 转义反斜杠
echo "Path: C:\\Users\\Alice" # 输出:Path: C:\Users\Alice

# 转义换行(在双引号中跨行)
echo "Line 1\
Line 2"                       # 输出:Line 1Line 2

⚠️ 注意:在双引号中,\n\t 等转义序列 不会 被解释为换行或制表符(除非用 echo -e$'...' 语法)。


✅ 3. 在单引号内部(Inside single quotes)

反斜杠完全失效 !单引号内的一切字符都被视为字面量(literal),包括 $*"\ 等。

bash 复制代码
echo 'Price: \$5'      # 输出:Price: \$5(反斜杠被原样输出)
echo 'He said \"Hi\"'  # 输出:He said \"Hi\"
echo '$HOME'           # 输出:$HOME(变量不展开)

# 甚至不能在单引号内包含单引号(必须用其他方式拼接)
echo 'It'\''s working' # 输出:It's working(技巧:结束单引号,插入转义的单引号,再开启单引号)

❌ 常见错误示例

bash 复制代码
# 错误:试图在单引号中转义
echo 'Today is $(date)'   # 输出:Today is $(date) --- 命令不会执行!

# 正确做法:改用双引号
echo "Today is $(date)"   # 正常执行命令替换

总结表

上下文 \ 是否有效? 示例
无引号 ✅ 对所有特殊字符有效 echo \**
双引号 " ✅ 仅对 $ ````` " \ 换行 有效 echo "\$HOME"$HOME
单引号 ' ❌ 完全无效,\ 被视为普通字符 echo '\$'\$

shell 参数展开$...

' $ '字符用于参数展开、命令替换或算术展开

要展开的参数名或符号可以被花括号{}括起来,括号是可选的,但它们有助于保护要展开的变量免受其后可能被解释为名称一部分的字符影响 。例如,如果第一个位置参数的值是' a ',那么 ${11} 会扩展为第十一个位置参数的值,而 $11 会扩展为' a1 '(第一个位置参数a和后面的1拼接而成)。

当使用花括号时,匹配的结束花括号 是第一个未被反斜杠转义不位于引号内 的' } ',并且不在嵌入的算术扩展、命令替换或参数扩展中。

语法级逃逸(转义与引号)

反斜杠转义: \} 被视为普通的字符 },而不是语法的终结。

引号包裹: "}"'}' 在 Bash 眼中只是字符串。

bash 复制代码
{
  echo "This is not the end: }" 
  echo "Still inside \{ "
} # 只有这个才是真正的结束

参数扩展的基本形式是${parameter},它将参数的值 进行替换。参数可以是如上所述的 shell 参数(参见 Shell Parameters)或数组引用(参见 Arrays)。

当参数是一个多位数的位置参数 ,或者参数后面跟着的字符不应被解释为参数名的一部分时必须使用花括号

讨论后续内容前,先了解nameref的概念

在 Bash 脚本中,nameref(名称引用)是一种特殊的变量属性 ,它允许你创建一个变量别名 。简单来说,如果你把变量 A 设置为变量 Bnameref,那么对 A 的任何操作(读、写、定义)实际上都是在操作 B。这主要通过 declare -n 命令来实现

bash 复制代码
target="Hello World"
declare -n ref=target  # ref 现在是 target 的引用

echo $ref              # 输出: Hello World
ref="Goodbye"          # 修改 ref 实际上修改了 target
echo $target           # 输出: Goodbye

在没有 nameref 之前,在 Bash 函数中修改外部变量通常需要用到复杂的 eval 命令,这非常危险且难以维护。nameref 提供了一种更优雅、更安全的方式。

在函数中修改传入的变量示例

bash 复制代码
update_value() {
 declare -n var=$1  # 将第一个参数视为一个引用
 var="Updated!"     # 修改引用指向的原始变量
}

my_data="Original"
update_value my_data   # 注意:传递的是变量名 "my_data",而不是值 "$my_data"
echo $my_data          # 输出: Updated!

间接扩展(展开)${!name}

间接扩展本质上就是 C 语言指针 的 Bash 版本:变量里存的是另一个变量的"名字"。

如果参数的第一个字符是感叹号(!),分两种情况:

完全间接展开
  • 该参数不是 一个 nameref,则引入一个间接引用的层级 。Bash 使用将参数其余部 分展开后得到的新参数;然后对这个新参数进行展开,并将该值用于后续的展开,而不是对原始参数进行展开 。这被称为 indirect expansion (间接展开)。该值会受到波浪线展开、参数展开、命令替换和算术展开的影响。

    To expand via indirection, use arrays, ${!name} or (for sh only) eval.

    例如${$i}是错误的写法,bash无法理解这种写法,但是${!i}是一种合法的写法,若参数i不是一个nameref,其效果为:

    如果$i取值为n,则${!i}就相当于$n,也就是说bash会将!i先计算出来一个名字作为变量名,然后计算这个新变量名的值

    这个过程涉及到变量名有原始的i以及间接扩展计算后得到的名字n

bash 复制代码
普通扩展:    ${var}      →  var的值

间接扩展:    ${!var}     →  var的值 → 把这个值当作变量名 → 该变量的值
                              ↓
                           两层跳转

例如

bash 复制代码
name="greeting"
greeting="Hello World"

echo ${name}      # 输出: greeting(普通扩展)
echo ${!name}     # 输出: Hello World(间接扩展)

# 执行过程:
# ${!name} 
#   → name 的值是 "greeting"
#   → 把 "greeting" 当作新变量名
#   → 扩展 ${greeting}
#   → 得到 "Hello World"

又比如在bash脚本中带编号地将脚本的参数打印出来(尽管下面的方案不是唯一的.)

bash 复制代码
# 带编号打印参数,是和使用C风格的for循环(借助$#获取参数个数)
for ((i=1;i<=$#;i++)) do
    # $i是参数编号, ${!i}是第i个的取值
    echo "arg $i:${!i}"
done

#朴素方案也可以
i=1
for arg in "$@"
do
    echo "arg $i:$arg"
    i=$((i+1))
done
bash 复制代码
# 场景:动态选择配置(变量名构造)
env="production"
#定义一组关于数据库的可用的变量名
development_db="localhost"
production_db="prod.server.com"
#根据环境构造对应的数据库变量名
db_var="${env}_db"        # db_var = "production_db"
echo ${!db_var}           # 输出: prod.server.com
不完全间接展开
  • 如果参数是一个 nameref,那么它会扩展为参数所引用的变量名,而不是完全进行间接展开,以保持兼容性

    bash 复制代码
    $ target="Hello World"
    declare -n ref=target
    
    $ declare -p ref
    declare -n ref="target"
    # 不完全间接展开
    $ echo ${!ref}
    target
    # 普通展开
    $ echo ${ref}
    Goodbye

对比两种间接展开语法

bash 复制代码
# 普通变量 + 间接扩展
target="hello"
pointer="target"
echo ${!pointer}    # 输出: hello(pointer不是nameref,执行完整间接扩展)

# Nameref 变量
declare -n ref=target
target="hello"
echo ${!ref}        # 输出: target(ref是nameref,只返回变量名,不是值!执行的是不完全间接扩展)
echo ${ref}         # 输出: hello(nameref 本身就自动解引用)

下面描述的${!prefix*}${!name[@]}的展开情况是例外。感叹号必须紧接在左大括号之后,才能引入间接引用。

特殊展开
语法 含义 示例
${!prefix*} 列出以 prefix 开头的变量名 ${!BASH*}
${!name[@]} 列出数组的所有索引/键 ${!arr[@]}
bash 复制代码
# ${!prefix*} - 列出变量名
MYAPP_HOST="localhost"
MYAPP_PORT="8080"
echo ${!MYAPP*}     # 输出: MYAPP_HOST MYAPP_PORT

# ${!name[@]} - 列出关联数组索引
declare -A user=([name]="Tom" [age]=25)
echo ${!user[@]}    # 输出: name age

展开总结

在以下每种情况下,语法中的word 会受到波浪线扩展、参数扩展、命令替换和算术扩展的影响。

当不进行子字符串扩展时,使用下面的形式(例如' :- '),Bash 会测试参数是否未设置或为空 。省略冒号则仅测试参数是否未设置

换句话说,如果包含冒号,运算符会测试参数是否存在且其值不为空;如果省略冒号,则仅测试参数是否存在。

根据 GNU Bash 参考手册:Shell Parameter Expansion 的内容。

Bash Shell 参数扩展 (Parameter Expansion) 总结表

bash除了${parameter}这种基础参数扩展,还提供了许多变体可用.

通常用在字符串上,有的也可以作用于数组或序列

关键符号说明

  • parameter:变量名。

  • word:默认值或替代字符串(可包含其他变量或命令替换)。

  • : (冒号):如果操作符包含冒号(如 :-,:=,:?,:+),则表示变量未定义 (unset)值为空 (null) 时触发(严格模式,word非空就能保证表达式非空);如果省略冒号(如 -),则仅在**变量未定义(unset)**时触发(宽松模式,允许空值)。

    可见,无论是否使用:,变量未定义的情况都做响应,定义单位空,则需要:才响应

  1. 间接引用:${!<parameter|prefix<@|*>},其中parameter表示完整变量名或nameref
  2. 变量检索${!<parameter|prefix<@|*>},prefix是变量的一部分(前缀),即根据变量名前缀检索相关变量
  3. 片段截取:${parameter:start[:length]},其中start可以是负整数(此时-号和:必须有空格隔开),0,正整数,length为正整数.
  4. 长度:${#parameter},其中parameter可以是特殊参数@|*或者数组序列array[@]或字符串
  5. 删除:${parameter<%|%%|#|##>pattern}
  6. 替换:${parameter</|//>pattern1/pattern2}
  7. 大小写转换:${parameter<^|^^|,|,,>pattern}
  8. 默认值处理:${parameter[:]<-|=|+|?>word}
  9. 参数变换:${parameter@operator}
间接引用
语法 说明 示例
${!parameter} 间接扩展 。将 parameter 的值作为新的变量名,并扩展该新变量的值。 a=b, b=hi,则 ${!a} 输出 hi
默认值与条件处理 (Default & Conditional)

利用本节语法可以简化条件判断,少写判断语句;在脚本命令行参数默认参数中常用

通常来说带有:比较常用

默认值分为直接返回(-)和赋值更新变量(=)

-:使用默认值(仅返回)

=:赋值默认值(并返回)

+:使用代替指(仅返回),和-行为相反

?:word还可以设置为提示性信息(报错信息)

语法 说明 示例 (假设 unset_var 未定义, empty="")
${parameter:-word} 使用默认值 。若变量未定义或为空,返回 word;否则返回变量值。 ${name:-default} (常用)
${parameter-word} 使用默认值 (仅限未定义) 。若变量未定义,返回 word;若变量为空,返回空。 ${empty-"def"} 输出空;${unset_var-"def"} 输出 def
${parameter:=word} 赋值默认值 。若变量未定义或为空,将 parameter 赋值为 word 并返回。 ${count:=0} (若 count 无值,设为0)
${parameter=word} 赋值默认值 (仅限未定义) 。若变量未定义,赋值为 word ${var=value}
${parameter:?word} 报错检查 。若变量未定义或为空,将 word 输出到 stderr 并退出脚本。 ${HOME:?Home undefined or empty}
${parameter?word} 报错检查 (仅限未定义)。若变量未定义,报错退出。 ${HOME:?Home undefined}
${parameter:+word} 使用替代值 。若变量有值 (非空),返回 word;否则返回空(不做任何事)。 ${debug:+"Debug mode on"}
${parameter+word} 使用替代值 (仅限已定义) 。若变量已定义(即使为空),返回 word ${var+"Exists"}
切片 (Substring Expansion)
语法 说明 示例 (str="abcdef")
${parameter:offset} 子串提取 。从第 offset 个字符开始截取到末尾(索引从0开始)。 ${str:2} -> cdef
${parameter:offset:length} 定长截取 。从第 offset 个字符开始,截取 length 个字符。(注意第二个参数不是索引,而是长度.) ${str:2:3} -> cde
${parameter: -offset} 倒数截取 。从尾部向前偏移(注意冒号后有空格,否则会被解释为前面的默认值功能)。 ${str: -2} -> ef
${array[@]:offset:length} 对索引数组切片 关联数组上做子串切片结果未定义
${@:offset:length} / ${*:offset:length} 对位置参数做子串式"切片"(返回若干个位置参数) offset 允许负数(从最后倒数);length<0 是错误;offset=0 时会把 $0 加进结果

offset/length 是算术表达式;offset<0 表示从末尾倒数;负 offset 要和冒号至少隔一个空格 避免被解析成 :-length<0 有特殊含义(按"从末尾倒数的终点"解释)

枚举和长度(Enumeration & Length)

注意prefix,name之间区别,以及@,*是否包裹在[]

形式 含义 关键规则
${!prefix*} / ${!prefix@} 展开为变量名列表 :所有名字以 prefix 开头的变量 默认用 IFS 首字符分隔;当用 @ 且在双引号内时,每个变量名作为独立词
${!name[@]} / ${!name[*]} name 是数组:展开为数组下标(key)列表;若不是数组:name 已设置则为 0,否则为空 同样:@ 在双引号内会逐个成词
${#p} 返回 $p 的长度(字符串长度或数组/序列元素数量) p@/* 时返回位置参数个数; 若为array[@] 返回元素个数;索引数组允许负下标(从末尾倒数)
模式移除 (Pattern Removal)

此功能用来截取或提取字符串的某个部分是方便的,尤其是头部片段和尾部片段,例如提取文件名或者后缀扩展名

配合迭代多次模式移除,可以提取出相对于原串的更靠中间的片段.

模式移除包括前缀移除和后缀移除,还可以选择贪婪(最长)和非贪婪(最短)模式,可以组合可得到4中移除模式

以下是可以在通配符中使用的元字符

  1. ***** :匹配任何字符串,包括空字符串。
  2. :匹配任意单个字符。
  3. [...]或[-]匹配方框内任意一个字符 。这被称为方括号表达式 ,其仅匹配单个字符

正则中常用的.在通配模式中不是元字符,没有特殊含义!

语法 说明 示例 (file="data.tar.gz")
${parameter#pattern} 最短前缀移除 。从头匹配,删除最短 匹配的 pattern ${file#*.} -> tar.gz
${parameter##pattern} 最长前缀移除 。从头匹配,删除最长 匹配的 pattern ${file##*.} -> gz
${parameter%pattern} 最短后缀移除 。从尾匹配,删除最短 匹配的 pattern ${file%.*} -> data.tar
${parameter%%pattern} 最长后缀移除 。从尾匹配,删除最长 匹配的 pattern ${file%%.*} -> data
查找与替换 (Search and Replace)

这部分和sed的替换命令供有相似之处.

语法 说明 示例 (path="/bin:/usr/bin")
${parameter/pat/str} 替换第一个 。将第一个匹配的 pat 替换为 str ${path/bin/local} -> /local:/usr/bin
${parameter//pat/str} 全局替换 。将所有匹配的 pat 替换为 str ${path//bin/local} -> /local:/usr/local
${parameter/#pat/str} 行首替换 。仅当 pat 匹配字符串开头时替换。 ${path/#\/bin/local}->local:/usr/bin
${parameter/%pat/str} 行尾替换 。仅当 pat 匹配字符串结尾时替换。 ${file/%bin/.sbin}->/bin:/usr/.sbin
${parameter/pat} 删除 。未指定 str,则删除匹配的内容。 ${path//:/} (删除所有冒号)->/bin/usr/bin

被替换的部分如果包含特殊字符,需要转移

例如

bash 复制代码
$ path="/bin:/usr/bin"
$ echo ${path/bin/local}
/local:/usr/bin
# 被替换的部分包含'/'字符,需要引用它,转移或者用引号包裹一下
$ echo ${path/#\/bin/local}
local:/usr/bin
$ echo ${path/#'/'bin/local}
local:/usr/bin
大小写转换 (Case Modification)

在 Bash (4.0+) 中,这四种语法用于大小写转换 。它们非常实用,能让你在不调用 trawk 等外部命令的情况下,直接在 shell 内部处理字符串。(但是这种语法的灵活性有限.)

核心逻辑很简单:

  • ^ 对应 大写 (Upper)
  • , 对应 小写 (Lower)
  • 单个符号(^,)只处理第一个字符
  • 双个符号(^^,,)处理所有匹配的字符

完整语法列表

  • ${parameter^pattern}
  • ${parameter^^pattern}
  • ${parameter,pattern}
  • ${parameter,,pattern}

这种扩展修改参数中字母字符的大小写 。首先,模式(pattern)会被展开以产生一个"如下面模式匹配中所述的"模式。

Bash 然后将参数扩展后的值中的字符与模式进行匹配,如下面所述。

如果字符与模式匹配,其大小写会被转换。模式不应尝试匹配多个字符

使用 ' ^ ' 会将匹配模式的字母转换为大写;' , ' 会将匹配模式的大写字母转换为小写。

  • ' ^ ' 和 ' , ' 变体则检查扩展后的值 中的第一个字符,如果匹配模式,则转换其大小写;(注意,这里要求第一次发生匹配的位置必须是第一个字符,如果第一次匹配不在第一个字符,或者压根没有出现被匹配的字符,则不处理)
  • ' ^^ ' 和 ', ' 变体则检查扩展后的值中的所有字符,并将每个匹配模式的字符转换大小写。

读者可能发现,如果要将位于中间(非首字符)的第一个pattern匹配的字符执行大/小写处理(其余位置即使发生匹配也不处理),则bash提供的这个语法无法实现.需要借助其他方法实现.比如调用外部工具或者编写循环.

如果省略了模式,则视为使用 ' ? ',它会匹配所有字符

如果${parameter}中的参数(parameter)是 ' @ ' 或 ' * '(在脚本中使用),则依次对每个位置参数执行大小写转换操作,扩展结果为转换后的列表。

如果参数是使用 ' @ ' 或 ' * ' 索引的数组变量,则依次对数组中的每个元素执行大小写转换操作,扩展结果为转换后的列表。

简化版本

语法 说明 示例 (var="hello WORLD")
${parameter^} 首字母大写。将第一个字符转为大写。 ${var^} -> Hello WORLD
${parameter^^} 全部大写。将所有字符转为大写。 ${var^^} -> HELLO WORLD
${parameter,} 首字母小写。将第一个字符转为小写。 ${var,} -> hello WORLD
${parameter,,} 全部小写。将所有字符转为小写。 ${var,,} -> hello world
  1. ${parameter^pattern}(首字母转大写)

仅当变量的第一个字符 匹配 pattern 时,将其转为大写。如果省略 pattern,默认匹配任意首字符。

bash 复制代码
name="hello world"
echo "${name^}" 
# 输出: Hello world (仅首字母 H 大写)

var="abc"
echo "${var^[a-z]}" 
# 输出: Abc
  1. ${parameter^^pattern}(全部转大写)

将变量中所有 匹配 pattern 的字符转为大写。

bash 复制代码
text="apple banana cherry"
echo "${text^^}" 
# 输出: APPLE BANANA CHERRY (全部转大写)

# 仅将文本中的 a 和 b 转为大写
echo "${text^^[ab]}" 
# 输出: Apple BAnAnA cherry
利用循环实现复杂需求

使用循环可以实现更加复杂的任务(也可以循环+外部命令)

例如,利用循环将第一个出现的字母n(无论出现在哪里)替换为大写N

bash 复制代码
text="apple-banana-cherry"
target="n"
result=""
# 标记是否已经找到第一个目标字符n的状态.
found=false

for (( i=0; i<${#text}; i++ )); do
    char="${text:i:1}" #截取第i个字符(设置步长为1)
    # 收集字符,重新组装.如果是第一次遇到目标字符,将其大写,并且将状态设置为已找到.其余情况直接收集拼接
    if [[ "$char" == "$target" && "$found" == false ]]; then
        result+="${char^}"
        found=true
    else
        result+="$char"
    fi
done

echo "$result" # 输出: apple-baNana-cherry
标准化用户输入

在编写脚本处理用户输入(如 y/n)时,这非常有用:

bash 复制代码
read -p "是否继续? (yes/no): " answer
# 将输入全部转为小写,方便后续判断
case "${answer,,}" in
    yes|y)
        echo "继续执行..."
        ;;
    *)
        echo "退出程序。"
        ;;
esac
把"元音"变大写
bash 复制代码
s='hello world'
printf '%s\n' "${s^^[aeiou]}"
所有小写字母变大写
bash 复制代码
s='hi 张三 abc XYZ'
printf '%s\n' "${s^^[[:lower:]]}"
位置参数和数组的大小写
  1. 位置参数 (Positional Parameters) 的案例

假设你写了一个脚本test.sh,并传入了多个参数:

bash 复制代码
# 脚本内容 test.sh
echo "原始参数: $@"

# 将每个参数的首字母转为大写
echo "转换后: ${@^}"

运行结果:

bash 复制代码
$ bash test.sh apple banana cherry
原始参数: apple banana cherry
转换后: Apple Banana Cherry
  1. 数组变量 (Array) 的案例

这是最常用的场景。你可以一次性对数组中的所有成员进行格式化。

bash 复制代码
apps=("docker" "nginx" "mysql")

# 1. 全部转为大写([@]的作用下,数组apps中的所有元素被拼接成一个长字符串,如果直接打印数组,只能打印数组的第一个元素)
echo "${apps[@]^^}"
# 输出: DOCKER NGINX MYSQL

# 2. 仅将每个元素的首字母转为大写
echo "${apps[@]^}"
# 输出: Docker Nginx Mysql

# 3. 带 pattern 的转换:仅将以 'n' 或 'm' 开头的元素首字母大写
echo "${apps[@]^[nm]}"
# 输出: docker Nginx Mysql

关于@*的区别,体现在前者处理后数组中的元素仍然可分.

bash 复制代码
apps=("docker" "nginx" "mysql")
# 使用数组语法
APPS=("${apps[@]^^}")

echo "${#APPS[@]}" # 输出: 3 (数组元素个数)
echo "${APPS[1]}"   # 输出: NGINX

# 使用循环也是可以的
APPS=()
for e in "${apps[@]^^}"; do 
    APPS+=("$e")  # 注意这里的括号,+=(..),它表示向数组追加元素
done

# 查看数组内容(使用 declare -p 可以查看到结构)
declare -p APPS
# 输出: declare -a APPS=([0]="DOCKER" [1]="NGINX" [2]="MYSQL")
echo "${APPS[@]}"
# 输出
DOCKER NGINX MYSQL
参数变换 (Parameter Transformation)

语法格式:${parameter@operator}(参数"变换/自省"操作符)

详细信息另见它文,这里仅作总结

bash@参数扩展@参数转换@参数扩展操作符-CSDN博客


操作符 名称 功能描述 示例 Bash版本
${param@Q} Quote 输出可作为输入重用的引用格式(比如回车符编码为\n) "line\"line2"->'line"line2' 4.4+
${param@E} Escape 展开转义序列(如\n, \t),需要把 \n 等转义真正变成换行时 \n -> 换行 4.4+
${param@P} Prompt 按提示符规则展开(如\u, \h),自定义提示符相关玩法 \u -> 用户名 4.4+
${param@A} Assign 输出变量的声明/赋值语句 ${str@A}->str='line"line2' 4.4+
${param@a} Attributes 输出变量的属性标志 echo ${arr@a}-> a 4.4+
${param@U} Uppercase 转换为全大写 hello->HELLO 5.1+
${param@u} Capitalize 首字母大写 hello->Hello 5.1+
${param@L} Lowercase 转换为全小写 Hello->hello 5.1+
${param@K}/${param@k} Keys 输出数组 key/value(K 更偏"可重用输入格式";k 会把 key/value 分成多个词) declare -A CONFIG=([debug]="true" [port]="8080" [host]="localhost" )->debug "true" port "8080" host "localhost" 5.1+

算数表达式$(())

该表达式的扩展方式与双引号括起来的表达式相同 ,但表达式中未转义的双引号字符 不会被特殊处理,而是会被移除

bash 复制代码
$ echo $(("3+5"))
8

由于$((expr))不是命令,所以不要求expr和括号()空格隔开

表达式中的所有标记都会进行参数和变量扩展、命令替换以及引号移除。结果将被视为待求值的算术表达式。

由于 Bash 处理双引号的方式可能会导致空字符串,因此算术扩展会将这些空字符串视为求值为 0 的表达式

算术扩展可以嵌套。

bash 复制代码
$ echo $(($((1+1))*5))=$((1+1))*5
10=2*5

bash shell扩展总结

命令行被分割成单词后 ,会对其进行扩展(处理)。Bash 执行以下扩展:

  1. 花括号展开
  2. 波浪号展开
  3. 参数和变量展开
  4. 命令替换
  5. 算术展开
  6. 单词分割
  7. 文件名展开
  8. 引号移除

展开的顺序是:大括号展开;波浪线展开、参数和变量展开、算术展开以及命令替换(从左到右依次进行);单词分割;文件名展开;以及引号移除。

在支持该功能的系统上,还有一种额外的扩展 可用:进程替换。这种扩展与波浪线、参数、变量、算术扩展和命令替换同时进行。

引号移除总是最后进行的 。它移除原始单词中的引号字符,而不是由其他扩展产生的引号,除非这些引号本身被引用了 (比如被\转义)。有关引号移除的更多细节,请参见引号移除部分。

只有[大括号扩展、单词分割和文件名扩展]会增加扩展后的单词数量;其他扩展都是将一个单词扩展为一个单词。

这一规则的例外情况是 "$@"$* (参见特殊参数)以及 "${name[@]}"${name[*]} (参见数组)的扩展。

bash 特殊参数

在 Bash 脚本和终端操作中,像 $$$? 这种符号被称为特殊变量特殊参数。它们由 Shell 自动维护,用来提供关于脚本运行状态、进程 ID 或命令执行结果的重要信息。

Bash 特殊变量和特殊参数参考表,按功能进行分类:(参考文档)


1. 状态与进程标识

这些变量用于监控命令的执行结果和获取进程信息。

变量 名称 详细说明
$? 退出状态 上一个命令的返回值(0 为成功,非 0 为失败)。
$$ 当前 PID 当前 Shell 脚本的进程 ID。
$! 后台 PID 最近一个放到后台运行的作业(job)的进程 ID。
$- 标志位 当前 Shell 使用的启动选项和标志(如 himBH)。
$_ 上个命令末尾 上一个执行命令的最后一个参数

2. 脚本参数与位置变量

当你在执行脚本(如 ./test.sh arg1 arg2)时,这些变量用于获取传入的信息。

变量 名称 详细说明
$0 脚本名 当前执行的脚本名称或 Shell 名称。
$1 - $n 位置参数 传入脚本的第 1 到第 n 个参数(超过 9 需用 ${10})。
$# 参数个数 传递给脚本或函数的参数总个数。
$* 所有参数 将所有参数视为一个 单词("$1 $2 $3")。
$@ 所有参数 将所有参数视为多个 独立的单词("$1" "$2" "$3"),推荐使用

3. 环境与内部变量

这些变量通常由 Shell 预定义,用于描述当前运行环境。

变量 名称 详细说明
$IFS 字段分隔符 决定 Bash 如何拆分字符串(默认是空格、制表符、换行符)。
$HOME 家目录 当前用户的家目录路径。
$PWD 当前路径 当前工作目录的绝对路径。
$USER 用户名 当前登录的用户名。
$RANDOM 随机数 生成一个 0 到 32767 之间的随机整数。
$SECONDS 运行时间 脚本自启动以来运行的秒数。

简单示例

bash 复制代码
#!/bin/bash
echo "脚本名称: $0"
echo "第一个参数: $1"
echo "参数总数: $#"
echo "当前进程 PID: $$"

# 执行一个注定失败的命令
ls /not_found_xyz
echo "上一条命令的退出状态: $?"
bash 复制代码
$ echo "I am $LOGNAME"
I am lhunath
$ echo 'I am $LOGNAME'
I am $LOGNAME

$ # boo
$ echo An open\ \ \ space
An open   space

# 命令替换
$ echo "My computer is $(hostname)"
My computer is Lyndir
# 重定向字符串boo到文件file
$ echo boo > file && cat file
boo
# 算数表达式
$ echo $(( 5 + 5 ))
10

$ (( 5 > 0 )) && echo "Five is greater than zero."
Five is greater than zero.

已弃用的特殊字符

可识别,但不建议使用

Bash 会将这组字符视为具有非字面 意义,但通常仅为向后兼容而保留。不建议使用这些字符,但它们经常出现在较旧或编写不佳的脚本中

字符 描述
命令替换 - 请使用 $( ) 代替。
[ ] Test 是旧版 test 命令的别名。常用于 POSIX shell 脚本中。缺少 [[ ]] 的许多功能。
$[ ] 算术表达式 - 请使用 $(( )) 代替。

此外:

  • 手册 --- Shell 语法
  • 特殊字符 ------这些字符对 Bash 具有特殊含义。通常情况下,Bash 会先解释这些字符的含义,然后在执行命令之前将其从命令中移除。
相关推荐
devmoon2 小时前
快速了解兼容 Ethereum 的 JSON-RPC 接口
开发语言·网络·rpc·json·区块链·智能合约·polkadot
dingdingfish2 小时前
Bash 学习 - 第2章:Definitions
bash·definition
aini_lovee2 小时前
基于MATLAB的材料晶粒组织生长与变化模拟:方法、实现与应用
开发语言·算法·matlab
1104.北光c°2 小时前
【黑马点评项目笔记 | 优惠券秒杀篇】构建高并发秒杀系统
java·开发语言·数据库·redis·笔记·spring·nosql
梦梦代码精2 小时前
Gitee 年度人工智能竞赛开源项目评选揭晓!!!
开发语言·数据库·人工智能·架构·gitee·前端框架·开源
2501_907136822 小时前
PDF增效工具 Quite imposing plus6
java·开发语言
常利兵2 小时前
Android Gradle 构建脚本现代化:Kotlin DSL (.kts) 与 Groovy DSL 深度对比与实战指南
android·开发语言·kotlin
csbysj20202 小时前
Ruby CGI 编程
开发语言
jiang_changsheng2 小时前
MCP协议的核心架构基础
c语言·开发语言·c++·python·comfyui