shell总结

主要给自己参考用。

变量

shell中变量都是字符串类型,在需要整数的地方会尝试转化为整数。
变量名大小写敏感,用=赋值,前后没有空白,否则会尝试将其识别为命令。
${var}的形式引用变量,在没有歧义的情况下,大括号可以省略。

只读变量

使用 readonly 可以将变量声明为只读变量,只读变量的值不能被改变。

$ rword="hello"
$ echo ${rword}
$ readonly rword
$ readonly #显示所有只读变量
$ rword="bye" #运行时报错

删除变量

使用 unset 可以删除变量,但它不能删除只读变量。
删除变量和将变量赋为空虽然都echo为空,但用set -o nounset开启变量检查的时候会有差别。(参考 shell脚本调试方法

变量类型

  • 局部变量 - 局部变量只有在变量所在的代码块或者函数中才可见,使用 local 声明;
  • 全局变量 - 用户自定义的普通变量默认是全局变量,可以在本文件中的其它位置引用;
  • 环境变量 - 所有的程序(包括shell启动的程序)都能访问环境变量。如果一个shell脚本设置了环境变量,需要用 export 命令来通知脚本的环境。env或printenv可以输出所有环境变量。

一些特殊变量

$_ 上一个命令的最后一个参数
$$ 或 $BASHPID 当前shell的进程id
$! 最近一个后台执行的异步命令的进程id
$RANDOM 生成一个 0 到 32767 之间的随机整数
$USER 当前用户的用户名
$PWD 当前工作目录
$IFS Internal Field Separator,默认为"空格、table、换行"
$? 上一条命令/函数的返回值
$# 脚本或函数的参数总数
$0 脚本名
$1 $2 $3 ... 脚本或函数的参数
$@ 脚本或函数的全部参数,被双引号包裹时相当于"$1 $2 $3 ..."
$* 脚本或函数的全部参数,被双引号包裹时相当于"$1" "$2" "$3" ...

shell允许在一个命令之前立即发生一个或多个变量赋值,这个赋值的影响是暂时的,只在命令存在期间改变环境变量,例如:

# 将一行读入user等变量中,如果不指定变量,会读入变量REPLY中
$ IFS=":" read user pw uid gid name home shell <<< root:x:0:0:root:/root:/bin/bash
$ echo $user $uid
root 0

输出变量的时候会将IFS的字符串替换成空格,所以其他情况下不建议修改IFS:

$ IFS=234
$ var=12345
$ echo $a
1   5

字符串

单引号和双引号

shell字符串可以用单引号 '',也可以用双引号 "",也可以不用引号。

  • '' :单引号里的任何字符都会原样输出,单引号中对变量引用是无效的,单引号中不能出现单引号;
  • "":双引号里可以引用变量,可以出现转义字符,双引号中不能出现双引号(除非进行转义)。

获取字符串长度

$ text="12345"
$ echo ${#text}
5

拼接字符串

将两个字符串并排放在一起就能实现拼接:

$ name="baidu"
$ url="http://baidu.com"
$ str1=$name$url #中间不能有空格
$ str2="$name $url" #如果被双引号包围,那么中间可以有空格
$ str3=$name": "$url #中间可以出现别的字符串
$ str4="${name}Script: ${url}/index.html" #这个时候需要给变量名加上大括号

截取子串

${string:position} 在string中, 从位置position开始提取子串
${string:position:length} 在string中, 从位置position开始提取长度为length的子串
${string:position:-end} 在string中,从位置position开始提取,到-end位置结束的子串

左边第一个字符的位置是 0,右边第一个字符的位置是 -1。
postion、-end、length需要是整数,或者结果为整数的算术表达式(可以有空格,可以有小括号)。

position参数如果是负数,需要和冒号之间加个空格,或者用一个算术表达式,比如 ${string: -2} 或 ${string:(-2)}表示string的最后两个字符。因为会优先识别为变量的默认值语法:${varname:-word},意为如果变量varname存在且不为空,则返回它的值,否则返回word。参考 变量的默认值

查找字符

$ text="hello"
$ echo `expr index "${text}" lo` #查找l或o第一次出现的位置
3

expr 的其他几种常用用法:

$ expr length "this is a test"
14
$ expr substr "this is a test" 3 5
is is
$ expr 14 % 9 # 空格用以分隔参数
5
$ expr 30 \* 3
90

删除子串

表达式含义
${string#substring}从string的开头,删除最短匹配substring的子串
${string##substring}从string的开头,删除最长匹配substring的子串
${string%substring}从string的结尾,删除最短匹配substring的子串
${string%%substring}从string的结尾,删除最长匹配substring的子串
$ path=/home/cam/book/file.name
$ echo ${path##*/}
file.name
$ echo ${path%/*}
/home/cam/book

替换子串

表达式含义
${string/substring/replacement}使用 replacement 来代替第一个匹配的 substring
${string//substring/replacement}使用 replacement 代替所有匹配的 substring
${string/#substring/replacement}如果 string 的前缀匹配 substring,那么就用 replacement 来代替匹配到的 substring
${string/%substring/replacement}如果 string 的后缀匹配 substring,那么就用 replacement 来代替匹配到的 substring

删除和替换子串时,substring支持通配符,原字符串都不变。

数组

bash支持索引数组,但仅是一维。数组下标从 0 开始,但不必连续。下标可以是整数或算术表达式,其值应大于等于 0。
bash的新版本也支持关联数组,关联数组使用字符串而不是整数作为数组索引,需要用declare -A提前声明。

创建数组

# 创建数组的不同方式
$ nums=([2]=2 [0]=0 [1]=1)
$ names=(hatter [5]=duchess alice) #hatter是0号位置,duchess是5号位置,alice是6号位置
$ colors=(red yellow "dark blue")
$ arr_name[0]=value1
$ arr_name[1]=value2
$ arr_name[23]=value3
$ mp3s=(*.mp3) #也可以使用通配符

读写数组成员时,如果没有指定下标,默认使用0号位置:

$ arr=(a b c)
$ echo $arr
a
$ echo $arr[0]
a[0]
$ echo ${arr[0]}
a
$ arr=z
$ echo ${arr[0]}
z

获得数组长度

${#nums[*]}${#nums[@]}

访问数组的部分元素

$ echo ${nums[*]:0:2} #从位置0开始,2个元素
0 1
$ echo ${nums[@]:0:2} #一样的
0 1

访问数组的所有元素

$ echo ${!colors[*]} #返回数据的成员序号
0 1 2
$ echo ${!colors[@]}
0 1 2
$ echo ${colors[*]}
red yellow dark blue
$ echo ${colors[@]}
red yellow dark blue

为了将数组中每个元素单独一行输出:

$ printf "+ %s\n" ${colors[*]}
+ red
+ yellow
+ dark
+ blue

尝试用引号包起来:

$ printf "+ %s\n" "${colors[*]}"
+ red yellow dark blue

试试 ${colors[@]}

$ printf "+ %s\n" "${colors[@]}"
+ red
+ yellow
+ dark blue

不被引号包裹时,二者相同,都相当于${colors[0]} ${colors[1]} ${colors[2]}
被引号包裹时,
"${colors[*]}"相当于"${colors[0]} ${colors[1]} ${colors[2]}"
"${colors[@]}"相当于"${colors[0]}" "${colors[1]}" "${colors[2]}"

所以向数组中添加元素可以这样:

$ colors=(white "${colors[@]}" green black)
$ echo ${#colors[@]}
6
$ echo ${colors[@]}
white red yellow dark blue green black

遍历数组可以这样:

for i in "${colors[@]}"; do
	echo $i
done

也可以用下标来遍历:

for i in ${!colors[*]}; do
	echo ${colors[i]}
done

追加数组成员

数组末尾追加成员,可以使用+=赋值运算符,它能够自动地把值追加到数组末尾。

$ foo=(a b c)
$ echo ${foo[@]}
a b c
$ foo+=(d e f)
$ echo ${foo[@]}
a b c d e f

从数组中删除元素

用 unset 命令来从数组中删除一个元素:

$ unset nums[0]
$ echo ${nums[@]}
1 2

括号/扩展

大括号扩展

大括号扩展{…}表示分别扩展成大括号里面的所有值,各个值之间使用逗号分隔,中间不能有空白,值可以为空,可以重复。
大括号可以嵌套,可以与模式扩展(参考fnmatch和glob模式匹配)联用,但先于模式扩展生效。
大括号扩展让生成任意的字符串成为可能。

$ echo beg{i,a,u}n
begin began begun

大括号扩展有一个简写形式{start…end…step},表示扩展成一个连续序列,step用来指定扩展的步长。

$ echo {0..5}
0 1 2 3 4 5
$ echo {00..8..2} #如果start和end为数字,且至少之一有前导0,数字会被扩展成二者最大宽度
00 02 04 06 08
$ echo {c..a} #支持字母,支持逆序
c b a
$ mkdir {2007..2009}-{01..12} #会创建3x12=36个目录

小括号

小括号中的命令将会新开一个子 shell 顺序执行,括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格。
这种用法可以让 进程脱离终端在后台运行

命令置换

命令置换允许我们对一个命令求值,并将其值置换到另一个命令或者变量赋值表达式中。当一个命令被``或$()包围时,命令置换将会执行。命令前后可以有空格。

$ now=`date +%T`
$ echo $now
$ now=$(date +%T)
$ echo $now

算数扩展

((…))语法可以进行整数的运算,会自动忽略内部的空格,不需要在变量名之前加上$,不过加上也不报错。

$ ((foo = 5 + 5))
$ echo $foo
10

((…))看起来有点像一个命令,只会返回成功(0)或失败(1),括号中表达式运算结果为非0,((…))返回成功,括号中表达式运算结果为0,((…))返回失败。可以放命令的地方,比如if,while,&&语句都可以放((…))。
如果要读取运算结果,需要将((…))改成$((…))。
((…))支持多种运算,比如比较运算、四则运算、逻辑运算、位运算等,与c语言中整数运算语法类似。细节参考 Bash 的算术运算

控制语句

命令的逻辑运算

命令控制操作符&&表示与,||表示或:
cmd1 && cmd2,cmd1执行成功后才会执行cmd2
cmd1 || cmd2,cmd1执行失败后才会执行cmd2

条件判断

[ ] 是bash内部命令,和 test 等同。这个命令把它的参数作为比较表达式或者作为文件测试,并且根据比较的结果来返回一个退出状态码。

[[ ]] 是bash关键字。[[ ]] 比 [ ] 更加通用,可以认为是 [ ] 的超集。使用 [[ ]] 而不是 [ ] 能够防止脚本中的许多逻辑错误。
比如,&&、||、<、> 操作符能够正常存在于 [[ ]] 中,但是不能出现在 [ ] 中,因为&&、||是命令操作符,<、>用于重定向。
[[ ]] 也支持正则表达式的判断,格式是 [[ string =~ regex ]]

字符串判断:
=、== 相等判断
!= 不等判断
-z 判断字符串长度是否为空(未被赋值或空字符串)
字符串本身 判断字符串是否非空(与-z相反)
-n 判断字符串非空,但用 [ ] 时注意要用引号括起来

$ [ -n $a ] && echo true || echo false
true
$ [ -n "$a" ] && echo true || echo false
false
$ [[ -n $a ]] && echo true || echo false
false

整数判断:
-eq 相等判断
-ne 不等判断
-gt 大于判断
-lt 小于判断
-ge 大于等于判断
-le 小于等于判断

文件判断:
-f 普通文件
-L 符号链接
-w 文件可写
更多 参考

逻辑运算符:
! 取非
-a 与运算
-o 或运算
&& 与运算,仅可用于[[ ]]
|| 或运算,仅可用于[[ ]]

选择控制

if语句

if commands; then
   符合该条件执行的语句
elif command; then
   符合该条件执行的语句
else
   符合该条件执行的语句
fi

then另起一行可以不要分号。elif、else分支都可以没有。

case语句

case expression in
	pattern )
		commands ;;
	pattern )
		commands ;;
	...
	* ) commands
esac

pattern与)直接可以无空格,pattern可以使用通配符。*)会匹配任意输入。
;;相当于c语言的break,如果想要fall-through,将其改为;;&

循环控制

for 循环

for arg in elem1 elem2 ... elemN
do
  语句
done

在每次循环的过程中,arg 依次被赋值为从 elem1elemN 。这些值还可以是通配符、大括号扩展、子命令的结果。
当然,我们还可以把for循环写在一行,但这要求do之前要有一个分号,就像下面这样:

for i in {1..5}; do echo $i; done

也可以像 c 语言那样使用 for,比如:

# (())中空格可以去掉
for (( i = 0; i < 10; i++ )); do
  echo $i
done

while 循环

while commands; do
  语句
done

until 循环

x=0
until [[ ${x} -ge 5 ]]; do
  echo ${x}
  x=`expr ${x} + 1`
done

select

select 帮助我们组织一个用户菜单:

select answer in elem1 elem2 ... elemN
do
  语句
done

select 会打印 elem1…elemN 以及它们的序列号到屏幕上,之后会提示用户输入。提示符是 $PS3。用户的选择结果会被保存到answer 中,选择的编号存入 REPLY 中。

break 和 continue

如果想提前结束一个循环或跳过某次循环执行,可以使用 break 和 continue 语句来实现。它们可以在任何循环和 select 中使用。

一个例子 ⌨️

calc(){
  PS3="choose the oper: "
  select oper in + - \* /
  do
    if [[ -z $oper ]]; then
      echo "${REPLY} is not support!"
      return 1
    fi
    echo -n "enter first num: " && read x
    echo -n "enter second num: " && read y
    case ${oper} in
      "+")
        result=$((x+y))
        ;;
      "-")
          result=$((x-y))
        ;;
      "*")
        result=`expr ${x} \* ${y}`
        ;;
      "/")
        result=$(expr ${x} / ${y})
        ;;
    esac
    break # 跳出select
  done
}
calc &&  echo "the result is: $result"

函数

[function] funname [()] {
    action
    [return int]
}

1.函数定义时,function 关键字和()至少有一个。
2.函数返回值类型只能为 0 到 255 的整数。如果不加return语句,或者return语句没写返回值,默认将以最后一条命令的运行结果作为函数返回值。
3.所有函数在使用前必须定义。

重定向

operatordescription
>重定向标准输出
2>重定向标准错误输出
n>重定向文件描述符n的输出
n>&重定向文件描述符n的输出到标准输出
n>&m重定向文件描述符n的输出到文件描述符m
&>重定向输出和错误输出
&>>以附加的形式重定向输出和错误输出
<重定向标准输入
n< file在文件描述符n上打开文件
<<Here 文档,从多行输入
<<<Here 字符串 ,从字符串输入
n<&拷贝标准输入到文件描述符n
n<&m拷贝文件名描述符m到文件描述符n

脚本内部也可以用 exec 命令来重定向输入输出,比如 exec 5<&4 会先将5号fd关闭,然后将4号fd重定向到5号上。一个例子:

exec 3<&0 < file.txt
read line
echo $line
exec <&3

上述代码中,将文件描述符3与标准输入关联,然后改变标准输入,使其从文件file.txt读取数据。
接下来读取了一行数据,并将其输出。最后恢复标准输入。

冒号是bash的一个内置命令,它什么效果都没有,总是返回成功,基本true命令一模一样(而false命令则总是返回失败)。
两个EOF(可用其它特殊成对字符替代)之间的内容通过<<输入给冒号,就相当于多行注释了:

:<<EOF
echo '这是多行注释'
echo '这是多行注释'
echo '这是多行注释'
EOF

另外Here文档内部会发生变量替换,如果不希望这样,可以把Here文档的开始标记放在单引号之中。

shell提供一种称为 noclobber 的特性,该特性可防止重定向时不经意地重写已存在的文件,打开该特性后若将输出重定向到已存在文件将报错。但是可以用 >| file强制覆盖:

$ touch tmp
$ set -o noclobber
$ echo hi > tmp
bash: tmp: Cannot overwrite existing file
$ echo hello >| tmp

Debug

如果想采用 debug 模式运行脚本,可以在其 shebang 中添加选项:

#!/bin/bash options

options 是一些可以改变 shell 行为的选项。比如 -v 表示 verbose ,在执行每条命令前,向 stderr 输出该命令 ,-x 表示 xtrace,在执行每条命令前,向 stderr 输出该命令以及该命令的扩展参数。

有时我们只需要 debug 脚本的一部分。这种情况下,使用 set 命令会很方便。这个命令可以启用或禁用选项。使用 - 启用选项,+ 禁用选项:

# 开启 debug
set -x
for (( i = 0; i < 3; i++ )); do
  echo ${i}
done
# 关闭 debug
set +x
for i in {1..5}; do printf ${i}; done
printf "\n"

更多内容可参考 shell的执行选项

一些快捷键

Ctrl + L:clear
Ctrl + Shift + PageUp:向上滚动
Ctrl + Shift + PageDown:向下滚动
Ctrl + U:从光标位置删除到行首
Ctrl + K:从光标位置删除到行尾
Ctrl + k:剪切光标位置到行尾的文本
Ctrl + u:剪切光标位置到行首的文本
Ctrl + y:在光标位置粘贴文本

Alt + $:即Alt + Shift + 4,变量名补全
Ctrl + p:显示上一个命令,与向上箭头效果相同(previous)
Ctrl + n:显示下一个命令,与向下箭头效果相同(next)
Alt + .:插入上一个命令的最后一个词
!string:执行最近一个以指定字符串string开头的命令(不含参数)

Bash允许用户定义自己的快捷键。全局的键盘绑定文件默认为/etc/inputrc,可以在主目录创建自己的键盘绑定文件.inputrc。如果定义了这个文件,需要在其中加入$include /etc/inputrc,保证全局绑定不会被遗漏。
.inputrc里面的快捷键可以像这样定义"\C-t":"pwd\n"表示将Ctrl + t绑定为运行pwd命令。
发现设置了ctrl+i的快捷键后,按table就相当于按了ctrl+i。

参考

一篇文章让你彻底掌握 shell 语言
Bash Shell编程入门
shell中的括号(小括号,中括号,大括号)
Shell 中 -n 条件判断的使用
阮一峰 Bash 脚本教程

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值