如何像专业人士一样使用 Bash 参数替换
$ 字符用于参数扩展、算术扩展和命令替换。您可以根据需要使用它来操作和扩展变量,而无需使用外部命令(如 perl、python、sed 或 awk)。本指南向您展示如何使用参数扩展修饰符来转换 Bash shell 变量以满足您的脚本需求。
句法
您可以使用变量来存储数据和配置选项。例如:
$ dest="/backups"
使用 echo 或 printf 命令显示变量值。例如:
$ echo "$dest"
或
$ printf "$dest\n"
要扩展的参数名称或符号(例如 $dest)可以用括号括起来。例如:
$ echo "Value ${dest}"
它是可选的,但可以保护要扩展的变量免受紧随其后的字符的影响,因为这些字符可能会被解释为名称的一部分。
1.设置默认 Shell 变量值
语法如下:
${parameter:-defaultValue} var=${parameter:-defaultValue}
如果未设置参数,则使用默认值。在此示例中,您的 shell 脚本采用命令行提供的参数。您希望提供默认值,以便可以使用最常用的值,而无需每次都输入它们。如果未设置或传递变量 $1,则使用 root 作为 u 的默认值:
u=${1:-root}
请考虑以下示例:
#!/bin/bash _jail_dir="${1:-/home/phpcgi}" echo "Setting php-cgi at ${_jail_dir}..." # rest of the script ...
您现在可以按如下方式运行该脚本:
./script.sh /jail # <--- set php jail at /jail dir ./script.sh /home/httpd/jail # <---- set php jail at /home/httpd/jail dir ./script.sh # <--- set php jail dir at /home/phpcgi (default)
这是另一个方便的例子:
_mkdir(){ local d="$1" # get dir name local p=${2:-0755} # get permission, set default to 0755 [ $# -eq 0 ] && { echo "$0: dirname"; return; } [ ! -d "$d" ] && mkdir -m "$p" -p "$d" } ## call it ## _mkdir "/var/www/php" 0644 _mkdir "/var/www/static" 0666 # set default permissions to 0755 in _mkdir() # _mkdir "/var/www/static"
使用此替代来创建故障安全功能并在脚本中提供缺少的命令行参数。
#1.1:设置默认值
语法如下:
${var:=value} var=${USER:=value}
如果变量还没有值,则使用赋值运算符 (:=) 为变量赋值。请尝试以下示例:
echo "$USER"
示例输出:
vivek
现在,如果 $USER 变量还没有值,则为其分配一个值 foo :
echo ${USER:=foo}
示例输出:
vivek
取消设置 $USER 的值:
unset USER echo "${USER:=foo}"
示例输出:
foo
这确保您的脚本始终有一个默认的合理值。
提示:${var:-defaultValue} 与 ${var:=defaultValue}
请注意,它不适用于位置参数参数:
var=${1:=defaultValue} ### FAIL with an error cannot assign in this way var=${1:-defaultValue} ### Perfect
2. 如果 $VAR 未传递,则显示错误消息
如果变量未定义或未传递,则可以使用以下语法停止执行 Bash 脚本:
${varName?Error varName is not defined} ${varName:?Error varName is not defined or is empty} ${1:?"mkjail: Missing operand"} MESSAGE="Usage: mkjail.sh domainname IPv4" ### define error message _domain=${2?"Error: ${MESSAGE}"} ### you can use $MESSAGE too
这用于给出未设置参数的错误消息。在此示例中,如果未传递 $1 命令行参数,则停止执行脚本并显示错误消息:
_domain="${1:?Usage: mknginxconf domainName}"
以下是示例脚本:
#!/bin/bash # Purpose: Wrapper script to setup Nginx Load Balancer # Author: Vivek Gite _root="/nas.pro/prod/scripts/perl/nginx" _setup="${_root}/createnginxconf.pl" _db="${_root}/database/text/vips.db" _domain="${1:?Usage: mknginxconf domainName}" ### die if domainName is not passed #### [ ! -f $_db ] && { echo "$0: Error $_db file not found."; exit 1; } line=$(grep "^${_domain}" $_db) || { echo "$0: Error $_domain not found in $_db."; exit 2; } # Get domain config info into 4 fields: # f1 - Domain Name| # f2 - IPv4Vip:httpPort:HttpsPort, IPv6Vip:httpPort:HttpsPort| # f3 - PrivateIP1:port1,PrivateIP2,port2,...PrivateIPN,portN| # f4 - LB Type (true [round robin] OR false [session]) # ------------------------------------------------------------------------------- IFS='|' read -r f1 f2 f3 f4 <<<"$line" # Do we want ssl host config too? IFS=':' set -- $f2 ssl="false" [ "$3" == "443" ] && ssl="true" # Build query d="$f1:$ssl:$f4" IFS=',' ips="$f3" # Call our master script to setup nginx reverse proxy / load balancer (LB) for given domain name $_setup "$d" "$ips"
2.1. 显示错误消息并运行命令
如果未设置 $2,则显示 $2 参数的错误消息并动态运行 cp 命令,如下所示:
#!/bin/bash _file="$HOME/.input" _message="Usage: chkfile commandname" # Run another command (compact format) _cmd="${2:? $_message $(cp $_file $HOME/.output)}" $_cmd "$_file"
3. 查找可变长度
您可以使用以下语法轻松找到字符串长度:
${#variableName} echo "${#variableName}" len="${#var}" # print it # echo "$len"
以下是添加 ftp 用户的示例 shell 脚本:
#!/bin/bash # Usage : Add a ftp user _fuser="$1" _fpass="$2" # die if username/password not provided [ $# -ne 2 ] && { echo "Usage: addftpuser username password"; exit 1;} # Get username length and make sure it always <= 8 [[ ${#_fuser} -ge 9 ]] && { echo "Error: Username should be maximum 8 characters in length. "; exit 2;} # Check for existing user in /etc/passwd /usr/bin/getent passwd "${_fuser}" &>/dev/null # Check exit status [ $? -eq 0 ] && { echo "Error: FTP username \"${_fuser}\" exists."; exit 3; } # Add user /sbin/useradd -s /sbin/nologin -m "${_fuser}" echo "${_fpass}" | /usr/bin/passwd "${_fuser}" --stdin
每个 Linux 或 UNIX 命令在正常或异常终止时都会返回一个状态。您可以在 shell 脚本中使用命令退出状态来显示错误消息或采取某种操作。在上面的例子中,如果 getent 命令成功,它会返回一个代码,告诉 shell 脚本显示错误消息。0 退出状态表示命令成功且没有任何错误。$? 保存先前执行的命令设置的返回值。
4. 删除模式($VAR 前面)
语法如下:
${var#Pattern} ${var##Pattern}
您可以根据给定的模式从 $var 前面剥离 $var。在此示例中,删除 /etc/ 部分并仅获取文件名,请输入:
f="/etc/resolv.conf" echo "${f#/etc/}"
我们看到文件名:
resolv.conf
第一种语法删除模式的最短部分,第二种语法删除模式的最长部分。考虑以下示例:
_version="20090128" _url="http://dns.measurement-factory.com/tools/dnstop/src/dnstop-${_version}.tar.gz"
您只想获取文件名,即 dnstop-20090128.tar.gz,输入(尝试删除 $_url 的最短部分):
echo "${_url#*/}"
示例输出:
/dns.measurement-factory.com/tools/dnstop/src/dnstop-20090128.tar.gz
现在尝试使用模式语法的最长部分:
echo "${_url##*/}"
示例输出:
dnstop-20090128.tar.gz
这对于不使用 /bin/basename 来获取脚本名称也很有用:
#!/bin/bash _self="${0##*/}" echo "$_self is called"
创建一个名为master.info的脚本,内容如下:
#!/bin/bash # Purpose: Display jail info as per softlink # Author: Vivek Gite _j="$@" # find out script name _self="${0##*/}" [ "$VERBOSE" == "1" ] && echo "Called as $_self for \"$_j\" domain(s)" for j in $_j do export _DOMAIN_NAME=$j source functions.sh init_jail # call appropriate functions as per script-name / softlink case $_self in uploaddir.info) echo "Upload dir for $j: $(get_domain_upload_dir)" ;; tmpdir.info) echo "/tmp dir for $j: $(get_domain_tmp_dir)" ;; mem.info) echo "$j domain mem usage (php+lighttpd): $(get_domain_mem_info)" ;; cpu.info) echo "$j domain cpu usage (php+lighttpd): $(get_domain_cpu_info)" ;; user.info) echo "$j domain user and group info: $(get_domain_users_info)" ;; diskquota.info) echo "$j domain disk quota info (mysql+disk): $(get_domain_diskquota_info)" ;; *) warn "Usage: $_self" esac done
最后,创建一个新的软链接,如下所示:
您现在可以按如下方式调用脚本:
$ sudo ln -s master.info uploaddir.info
$ sudo ln -s master.info tmpdir.info
$ sudo ln -s master.info mem.info
....
..
$ sudo ./mem.info example.com
$ ./cpu.info example.com example.com
#4.1:删除模式($VAR 的背面)
语法如下:
${var%pattern} ${var%%pattern}
与上面完全相同,只是它适用于 $var 的后面。在此示例中,从 $FILE 中删除 .tar.gz,请输入:
FILE="xcache-1.3.0.tar.gz" echo "${FILE%.tar.gz}"
以下是我得到的结果:
xcache-1.3.0
使用 bash for 循环将所有 *.perl 文件重命名为 *.pl ,因为 Apache Web 服务器配置为仅使用 .pl 文件而不是 .perl 文件名:
for p in /scripts/projects/.devl/perl/*.perl do mv "$p" "${p%.perl}.pl" done
您可以按如下方式组合它们来创建构建脚本:
#!/bin/bash # Usage: Build suhosin module for RHEL based servers # Author: Vivek Gite # ---- # Set default value for $2 VERSION="-${2:-0.9.31}" URL="http://download.suhosin.org/suhosin${VERSION}.tgz" vURL="http://download.suhosin.org/suhosin${VERSION}.tgz.sig" # Get tar ball names FILE="${URL##*/}" vFILE="${vURL##*/}" DLHOME="/opt" SOFTWARE="suhosin" # Remove .tgz and get dir name DEST="${FILE%.tgz}" # Download software wget "$URL" -O "${DLHOME}/$FILE" wget "$vURL" -O "${DLHOME}/$vFILE" # Extract it tar -zxvf "$FILE" cd "$DEST" # Build it and install it phpize --clean && phpize && ./configure && make && read -rp "Update/Install $SOFTWARE [Y/n] ? " answer shopt -s nocasematch [[ $answer =~ y|es ]] && make install shopt -u nocasematch
如果打开 nocasematch 选项,shell 在执行 case 或 [[ 条件表达式时进行匹配时会以不区分大小写的方式匹配模式。
5.查找和替换
语法如下:
${varName/Pattern/Replacement} ${varName/word1/word2} ${os/Unix/Linux}
查找单词 unix 并替换为 linux,输入:
x="Use unix or die" sed 's/unix/linux/' <<<$x
您可以避免使用 sed,如下所示:
echo "${x/unix/linux}" out="${x/unix/linux}" echo "${out}"
要替换所有匹配的模式,请输入:
out="${x//unix/linux}"
您可以使用它来重命名或删除文件
y=/etc/resolv.conf cp "${y}" "${y/.conf/.conf.bak}"
下面是另一个示例:
# RHEL php modules path _php_modules="/usr/lib64/php/modules" for i in $_php_modules/* do p="${i##*/}" ## Get module name ini="/etc/php.d/${p/so/ini}" ## Get ini file by replacing .so with .ini extension # make sure file exists [ ! -f "$ini" ] && echo "$i php module exists but $ini file not found." done
以下函数在 chrooted php-cgi 进程中安装所需的模块
install_php_modules(){ # get jail name local n="${_chrootbase}/${d##/}" local p="" local ini="" # enable only ${_php_modules_enabled} php modules and delete other .ini files if exists in jail for i in $_php_modules/* do p="${i##*/}" ini="$n/etc/php.d/${p/so/ini}" # find out if module is enabled or not if [[ ${_php_modules_enabled} = *${p}* ]] then [ "$VERBOSE" == "1" ] && echo " [+] Enabling php module $p" $_cp -f "$i" "$n/${_php_modules##/}" ## install it copy_shared_libs "$i" ## get shared libs in jail too else [ -f "${ini}" ] && $_rm -f "${ini}" ## if old .ini exists in jail, just delete it fi done }
6. 子串起始字符
语法如下:
${parameter:offset} ${parameter:offset:length} ${variable:position} var=${string:position}
从偏移量指定的字符开始扩展为最多为参数的长度个字符。
base="/backup/nas" file="/data.tar.gz" #### strip extra slash from $file #### path="${base}/${file:1}"
仅提取工艺词:
x="example.com" echo ${x:3:5}"
要提取电话号码,请输入:
phone="022-124567887" # strip std code echo "${phone:4}"
7. 获取匹配的变量名列表
想要获取名称以前缀开头的变量的名称?尝试:
VECH="Bus" VECH1="Car" VECH2="Train" echo "${!VECH*}"
8. 大写字母转换为小写字母或小写字母转换为大写字母
使用以下语法将小写字符转换为大写:
name="vivek" # Turn vivek to Vivek (only first character to uppercase) echo "${name^}" # Turn vivek to VIVEK (uppercase) echo "${name^^}" echo "Hi, $USERNAME" echo "Hi, ${USERNAME^}" echo "Hi, ${USERNAME^^}" # Convert everything to lowercase dest="/HOME/Vivek/DaTA" echo "Actual path: ${dest,,}" # Convert only first character to lowercase src="HOME" echo "${src,}"
仅当 $dest 中的第一个字符是大写字母“H”时才转换它:
dest="Home" echo "${dest,H}" dest="Fhome" echo "${dest,H}"
请参阅“ Shell 脚本:将大写字母转换为小写字母”以了解更多信息。
摘要:字符串操作和扩展变量
为方便您参考,以下是所有方便使用的 bash 参数替换运算符。全部尝试一下;像专业人士一样提高您的脚本技能:
多变的 | 描述 |
---|---|
${parameter:-defaultValue} | 获取默认 shell 变量值 |
${parameter:=defaultValue} | 设置默认 shell 变量值 |
${parameter:?"Error Message"} | 如果未设置参数,则显示错误消息 |
${#var} | 查找字符串的长度 |
${var%pattern} | 从最短尾部(末端)图案中移除 |
${var%%pattern} | 从最长的后部(末端)图案中移除 |
${var:num1:num2} | 子字符串 |
${var#pattern} | 从最短前片图案中移除 |
${var##pattern} | 从最长前部图案中移除 |
${var/pattern/string} | 查找并替换(仅替换第一次出现的情况) |
${var//pattern/string} | 查找并替换所有匹配项 |
${!prefix*} | 扩展为名称以前缀开头的变量的名称。 |
${var,} ${var,pattern} |
将第一个字符转换为小写。 |
${var,,} ${var,,pattern} |
将所有字符转换为小写。 |
${var^} ${var^pattern} |
将第一个字符转换为大写。 |
${var^^} ${var^^pattern} |
将所有字符转换为大写。 |
参考:
请使用 man 命令或 help 命令阅读 bash 手册页:
$ man bash
$ help command-name-here
参见:
- 使用 ShellCheck lint 脚本分析工具改进你的 bash/sh shell 脚本
- Linux shell 脚本 wiki
- UNIX/Linux bash 构建脚本示例
- 适用于 bash 4 的 oo 风格字符串库
- Bash 手册页