Shell编程学习
本文记录博主学习基础Shell知识笔记,参考于C语言中文网http://c.biancheng.net
本文目录
一、Shell变量
1.定义变量
Shell 支持以下三种定义变量的方式:
variable=value
variable='value'
variable="value"
如果 value 不包含任何空白符(例如空格、Tab 缩进等),那么可以不使用引号;如果 value 包含了空白符,那么就必须使用引号包围起来。
注意,赋值号=的周围不能有空格
Shell 变量的命名规范和大部分编程语言都一样:
变量名由数字、字母、下划线组成;
必须以字母或者下划线开头;
不能使用 Shell 里的关键字(通过 help 命令可以查看保留关键字)。
2.使用变量
使用一个定义过的变量,只要在变量名前面加美元符号$即可,如:
author="严长生"
echo $author
echo ${author}
变量名外面的花括号{ }是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:
skill="Java"
echo "I am good at ${skill}Script"
如果不给 skill 变量加花括号,写成echo "I am good at $skillScript",解释器就会把 $skillScript 当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。
推荐给所有变量加上花括号{ },这是个良好的编程习惯。
定义或赋值变量时无需使用’$’ ,只有在使用变量时才需使用
3.单引号和双引号的区别
#!/bin/bash
url="http://c.biancheng.net"
website1='C语言中文网:${url}'
website2="C语言中文网:${url}"
echo $website1
echo $website2
运行结果:
C语言中文网:${url}
C语言中文网:http://c.biancheng.net
以单引号’ '包围变量的值时,单引号里面是什么就输出什么, 即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合定义显示纯字符串的情况,即不希望解析变量、命令等的场景。
以双引号" "包围变量的值时,输出时会先解析里面的变量和命令, 而不是把双引号中的变量名和命令原样输出。这种方式比较适合字符串中附带有变量和命令并且想将其解析后再输出的变量定义。
如果变量的内容是数字,那么可以不加引号;如果真的需要原样输出就加单引号;其他没有特别要求的字符串等最好都加上双引号,定义变量时加双引号是最常见的使用场景。
4.将命令的结果赋值给变量
Shell 也支持将命令的执行结果赋值给变量,常见的有以下两种方式:
variable=`command`
variable=$(command)
第一种方式把命令用反引号` `包围起来,反引号和单引号非常相似,容易产生混淆,所以不推荐使用这种方式;第二种方式把命令用$()包围起来,区分更加明显,所以推荐使用这种方式。
例如,在 demo 目录中创建了一个名为 log.txt 的文本文件,用来记录我的日常工作。下面的代码中,使用 cat 命令将 log.txt 的内容读取出来,并赋值给一个变量,然后使用 echo 命令输出。
[mozhiyan@localhost ~]$ cd demo
[mozhiyan@localhost demo]$ log=$(cat log.txt)
[mozhiyan@localhost demo]$ echo $log
严长生正在编写Shell教程,教程地址:http://c.biancheng.net/shell/
[mozhiyan@localhost demo]$ log=`cat log.txt`
[mozhiyan@localhost demo]$ echo $log
严长生正在编写Shell教程,教程地址:http://c.biancheng.net/shell/
二、特殊变量
Shell 特殊变量及其含义
变量 | 含义 |
---|---|
$0 | 当前脚本的文件名。 |
$n(n≥1) | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2。如果参数个数太多,达到或者超过了 10 个,那么就得用${n}的形式来接收了,例如 ${10}、${23}。{ }的作用是为了帮助解释器识别参数的边界,这跟使用变量时加{ }是一样的效果。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。 |
$@ | 传递给脚本或函数的所有参数。当被双引号" "包含时,$@ 与 $* 稍有不同 |
$? | 上个命令的退出状态,或函数的返回值 |
$$ | 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。 |
- shell变量 $*与 $@的区别
$*和$@是shell脚本的特殊变量,作用都是获取传递给脚本或函数的所有参数。
在没有双引号包裹时,$*与 $@相同:都是数组
被双引号包裹时,$* 与 $@不同:“$@” 为数组,"$*"为一个字符串 - $? 用来获取上一个命令的退出状态,或者上一个函数的返回值。
所谓退出状态,就是上一个命令执行后的返回结果。退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败返回 1,
三、Shell字符串详解
1.引号的区别
1、由单引号’ '包围的字符串:
任何字符都会原样输出,在其中使用变量是无效的。
字符串中不能出现单引号,即使对单引号进行转义也不行。
2、由双引号" "包围的字符串:
如果其中包含了某个变量,那么该变量会被解析(得到该变量的值),而不是原样输出。
字符串中可以出现双引号,只要它被转义了就行。
3、不被引号包围的字符串
不被引号包围的字符串中出现变量时也会被解析,这一点和双引号" "包围的字符串一样。
字符串中不能出现空格,否则空格后边的字符串会作为其他变量或者命令解析。
2.获取字符串长度
在 Shell 中获取字符串长度很简单,具体方法如下:
${#string_name}
3.字符串拼接
在 Shell 中你不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接
#!/bin/bash
name="Shell"
url="http://c.biancheng.net/shell/"
str1=$name$url #中间不能有空格
str2="$name $url" #如果被双引号包围,那么中间可以有空格
str3=$name": "$url #中间可以出现别的字符串
str4="$name: $url" #这样写也可以
str5="${name}Script: ${url}index.html" #这个时候需要给变量名加上大括号
echo $str1
echo $str2
echo $str3
echo $str4
echo $str5
运行结果:
Shellhttp://c.biancheng.net/shell/
Shell http://c.biancheng.net/shell/
Shell: http://c.biancheng.net/shell/
Shell: http://c.biancheng.net/shell/
ShellScript: http://c.biancheng.net/shell/index.html
4.Shell字符串截取
Shell 截取字符串通常有两种方式:从指定位置开始截取和从指定字符(子字符串)开始截取。
汇总
格式 | 说明 |
---|---|
${string: start :length} | 从 string 字符串的左边第 start 个字符开始,向右截取 length 个字符。 |
${string: start} | 从 string 字符串的左边第 start 个字符开始截取,直到最后。 |
${string: 0-start :length} | 从 string 字符串的右边第 start 个字符开始,向右截取 length 个字符。 |
${string: 0-start} | 从 string 字符串的右边第 start 个字符开始截取,直到最后。 |
${string#*chars} | 从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。 |
${string##*chars} | 从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 右边的所有字符。 |
${string%*chars} | 从 string 字符串第一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。 |
${string%%*chars} | 从 string 字符串最后一次出现 *chars 的位置开始,截取 *chars 左边的所有字符。 |
四、数组
1.数组的基本操作
- 数组的定义
array_name=(ele1 ele2 ele3 … elen)
nums=(29 100 13 8 91 44)
# Shell 是弱类型的,它并不要求所有数组元素的类型必须相同
arr=(20 56 "http://c.biancheng.net/shell/")
nums[6]=88
ages=([3]=24 [5]=19 [10]=12)
- 获取数组
${array_name[index]}
n=${nums[2]}
# 使用@或*可以获取数组中的所有元素
${nums[*]}
${nums[@]}
#!/bin/bash
nums=(29 100 13 8 91 44)
echo ${nums[@]} #输出所有数组元素
nums[10]=66 #给第10个元素赋值(此时会增加数组长度)
echo ${nums[*]} #输出所有数组元素
echo ${nums[4]} #输出第4个元素
# 运行结果:
29 100 13 8 91 44
29 100 13 8 91 44 66
91
- 获取数组的长度
${#array_name[@]}
${#array_name[*]} - 数组拼接
拼接数组的思路是:先利用@或*,将数组扩展成列表,然后再合并到一起
array_new=(${array1[@]} ${array2[@]})
array_new=(${array1[*]} ${array2[*]})
#!/bin/bash
array1=(23 56)
array2=(99 "http://c.biancheng.net/shell/")
array_new=(${array1[@]} ${array2[*]})
echo ${array_new[@]} #也可以写作 ${array_new[*]}
# 运行结果:
# 23 56 99 http://c.biancheng.net/shell/
- 删除数组元素
unset array_name[index]
五、内建命令
1. alias命令
- 作用:alisa 用来给命令创建一个别名
alias new_name='command'
# 比如,一般的关机命令是shutdown-h now,写起来比较长,这时可以重新定义一个关机命令,以后就方便多了。
alias myShutdown='shutdown -h now'
2. echo命令
1.作用:用来在终端输出字符串,并在最后默认加上换行符
#!/bin/bash
name="Shell教程"
url="http://c.biancheng.net/shell/"
echo "读者,你好!" #直接输出字符串
echo $url #输出变量
echo "${name}的网址是:${url}" #双引号包围的字符串中可以解析变量
echo '${name}的网址是:${url}' #单引号包围的字符串中不能解析变量
# 读者,你好!
# http://c.biancheng.net/shell/
# Shell教程的网址是:http://c.biancheng.net/shell/
# ${name}的网址是:${url}
echo 命令输出结束后默认会换行,如果不希望换行,可以加上-n参数
解析转义字符 -e
echo -e "hello \nworld"
3. read命令
- 作用:用来从标准输入中读取数据并赋值给变量。如果没有进行重定向,默认就是从键盘读取用户输入的数据;如果进行了重定向,那么可以从文件中读取数据。
- 用法
read [-options] [variables]
- options表示选项,如下表所示;variables表示用来存储数据的变量,可以有一个,也可以有多个。
- options和variables都是可选的,如果没有提供变量名,那么读取的数据将存放到环境变量 REPLY 中。
- read 命令支持的选项
选项 | 说明 |
---|---|
-a array | 把读取的数据赋值给数组 array,从下标 0 开始。 |
-d delimiter | 用字符串 delimiter 指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter)。 |
-e | 在获取用户输入的时候,对功能键进行编码转换,不会直接显式功能键对应的字符。 |
-n num | 读取 num 个字符,而不是整行字符。 |
-p prompt | 显示提示信息,提示内容为 prompt。 |
-r | 原样读取(Raw mode),不把反斜杠字符解释为转义字符。 |
-s | 静默模式(Silent mode),不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这是很有必要的。 |
-t seconds | 设置超时时间,单位为秒。如果用户没有在指定时间内输入完成,那么 read 将会返回一个非 0 的退出状态,表示读取失败。 |
-u fd | 使用文件描述符 fd 作为输入源,而不是标准输入,类似于重定向。 |
4. exit命令
- 作用: 用来退出当前 Shell 进程,并返回一个退出状态;使用$?可以接收这个退出状态。
- exit 命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
- 一般情况下,退出状态为 0 表示成功,退出状态为非 0 表示执行失败(出错)了。
- exit 退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败
- Shell 进程执行出错时,可以根据退出状态来判断具体出现了什么错误
5.declare和typeset命令
1.作用:用法相同,都用来设置变量的属性, typeset 已经被弃用了,建议使用 declare 代替。
declare [+/-] [aAfFgilprtux] [变量名=变量值]
# 其中,-表示设置属性,+表示取消属性,aAfFgilprtux都是具体的选项,
选项 | 含义 |
---|---|
-f [name] | 列出之前由用户在脚本中定义的函数名称和函数体。 |
-F [name] | 仅列出自定义函数名称。 |
-g name | 在 Shell 函数内部创建全局变量。 |
-p [name] | 显示指定变量的属性和值。 |
-a name | 声明变量为普通数组。 |
-A name | 声明变量为关联数组(支持索引下标为字符串)。 |
-i name | 将变量定义为整数型。 |
-r name[=value] | 将变量定义为只读(不可修改和删除),等价于 readonly name。 |
-x name[=value] | 将变量设置为环境变量,等价于 export name[=value]。 |
# 将变量声明为整数并进行计算。
#!/bin/bash
declare -i m n ret #将多个变量声明为整数
m=10
n=30
ret=$m+$n
echo $ret
# 运行结果:
# 40
# 将变量定义为只读变量。
[c.biancheng.net]$ declare -r n=10
[c.biancheng.net]$ n=20
bash: n: 只读变量
[c.biancheng.net]$ echo $n
10
# 显示变量的属性和值。
[c.biancheng.net]$ declare -r n=10
[c.biancheng.net]$ declare -p n
declare -r n="10"
六、数学运算
1.运算符号
Shell 和其它编程语言不同,Shell 不能直接进行算数运算,必须使用数学计算命令
算术运算符 | 说明/含义 |
---|---|
+、- | 加法(或正号)、减法(或负号) |
*、/、% | 乘法、除法、取余(取模) |
** | 幂运算 |
++、– | 自增和自减,可以放在变量的前面也可以放在变量的后面 |
!、&&、|| | 逻辑非(取反)、逻辑与(and)、逻辑或(or) |
<、<=、>、>= | 比较符号(小于、小于等于、大于、大于等于) |
==、!=、= | 比较符号(相等、不相等;对于字符串,= 也可以表示相当于) |
<<、>> | 向左移位、向右移位 |
~、 | 、 &、^ |
=、+=、-=、*=、/=、%= | 赋值运算符,例如 a+=1 相当于 a=a+1,a-=1 相当于 a=a-1 |
2.数学计算命令
运算操作符/运算命令 | 说明 |
---|---|
(( )) | 用于整数运算,效率很高,推荐使用。 |
let | 用于整数运算,和 (()) 类似。 |
$[] | 用于整数运算,不如 (()) 灵活。 |
expr | 可用于整数运算,也可以处理字符串。比较麻烦,需要注意各种细节,不推荐使用。 |
bc | Linux下的一个计算器程序,可以处理整数和小数。Shell 本身只支持整数运算,想计算小数就得使用 bc 这个外部的计算器。 |
declare -i | 将变量定义为整数,然后再进行数学运算时就不会被当做字符串了。功能有限,仅支持最基本的数学运算(加减乘除和取余),不支持逻辑运算、自增自减等,所以在实际开发中很少使用。 |
3.(())符号
1.用法:
表达式可以只有一个,也可以有多个,多个表达式之间以逗号,分隔。对于多个表达式的情况,以最后一个表达式的值作为整个 (( )) 命令的执行结果。
可以使用$获取 (( )) 命令的结果,这和使用$获得变量值是类似的。
在 (( )) 中使用变量无需加上$前缀,(( )) 会自动解析变量名,这使得代码更加简洁,也符合程序员的书写习惯。
七、if else语句
Shell 支持选择结构,并且有两种形式,分别是 if else 语句和 case in 语句
1.if 语句
最简单的用法就是只使用 if 语句,它的语法格式为:
# 写法一
if condition
then
statement(s)
fi
#写法二
if condition; then
statement(s)
fi
# 请注意 condition 后边的分号;
#当 if 和 then 位于同一行的时候,这个分号是必须的,否则会有语法错误。
condition是判断条件,如果 condition 成立(返回“真”),那么 then 后边的语句将会被执行;如果 condition 不成立(返回“假”),那么不会执行任何语句。
- if else 语句
# 如果 condition 成立,那么 then 后边的 statement1 语句将会被执行;
#否则,执行 else 后边的 statement2 语句。
if condition
then
statement1
else
statement2
fi
- if elif else 语句
if condition1
then
statement1
elif condition2
then
statement2
elif condition3
then
statement3
……
else
statementn
fi
2.test命令
- 作用:test 是 Shell 内置命令,用来检测某个条件是否成立,可以进行数值、字符串和文件三个方面的检测。
- 语法:
- test expression
- [ expression ]
注意[]和expression之间的空格,这两个空格是必须的,否则会导致语法错误。[]的写法更加简洁,比 test 使用频率高。
- 与文件检测相关的 test 选项
文件类型判断
选 项 | 作 用 |
---|---|
-b filename | 判断文件是否存在,并且是否为块设备文件。 |
-c filename | 判断文件是否存在,并且是否为字符设备文件。 |
-d filename | 判断文件是否存在,并且是否为目录文件。 |
-e filename | 判断文件是否存在。 |
-f filename | 判断文件是否存在,井且是否为普通文件。 |
-L filename | 判断文件是否存在,并且是否为符号链接文件。 |
-p filename | 判断文件是否存在,并且是否为管道文件。 |
-s filename | 判断文件是否存在,并且是否为非空。 |
-S filename | 判断该文件是否存在,并且是否为套接字文件。 |
文件权限判断
选 项 | 作 用 |
---|---|
-r filename | 判断文件是否存在,并且是否拥有读权限。 |
-w filename | 判断文件是否存在,并且是否拥有写权限。 |
-x filename | 判断文件是否存在,并且是否拥有执行权限。 |
-u filename | 判断文件是否存在,并且是否拥有 SUID 权限。 |
-g filename | 判断文件是否存在,并且是否拥有 SGID 权限。 |
-k filename | 判断该文件是否存在,并且是否拥有 SBIT 权限。 |
文件比较
选 项 | 作 用 |
---|---|
filename1 -nt filename2 | 判断 filename1 的修改时间是否比 filename2 的新。 |
filename -ot filename2 | 判断 filename1 的修改时间是否比 filename2 的旧。 |
filename1 -ef filename2 | 判断 filename1 是否和 filename2 的 inode 号一致,可以理解为两个文件是否为同一个文件。这个判断用于判断硬链接是很好的方法 |
- 与数值比较相关的 test 选项
选 项 | 作 用 |
---|---|
num1 -eq num2 | 判断 num1 是否和 num2 相等。 |
num1 -ne num2 | 判断 num1 是否和 num2 不相等。 |
num1 -gt num2 | 判断 num1 是否大于 num2 。 |
num1 -lt num2 | 判断 num1 是否小于 num2。 |
num1 -ge num2 | 判断 num1 是否大于等于 num2。 |
num1 -le num2 | 判断 num1 是否小于等于 num2。 |
注意,test 只能用来比较整数,小数相关的比较还得依赖 bc 命令。
- 与字符串判断相关的 test 选项
选 项 | 作 用 |
---|---|
-z str | 判断字符串 str 是否为空。 |
-n str | 判断宇符串 str 是否为非空。 |
str1 = str2 ; str1 == str2 | =和==是等价的,都用来判断 str1 是否和 str2 相等。 |
str1 != str2 | 判断 str1 是否和 str2 不相等。 |
str1 \> str2 | 判断 str1 是否大于 str2。\>是>的转义字符,这样写是为了防止>被误认为成重定向运算符。 |
str1 \< str2 | 判断 str1 是否小于 str2。同样,\<也是转义字符 |