主要参考
Shell 可以通过调用其他程序,进行无限扩展
主要用于开发自动化的小工具
常见的 Shell 有sh、bash、csh、tcsh、ash
等
Shell 就是这样的一款软件,不同的组织机构开发了不同的 Shell,它们各有所长,有的占用资源少,有的支持高级编程功能,有的兼容性好,有的重视用户体验。
Shell 既是一种脚本编程语言,也是一个连接内核和用户的软件。
现代的 Linux 上,sh 已经被 bash 代替,/bin/sh
往往是指向/bin/bash
的符号链接
cat /etc/shells
echo $SHELL
对于普通用户,Base shell 默认的提示符是美元符号
$
;对于超级用户(root 用户),Bash Shell 默认的提示符是井号#
- PS1 控制最外层命令行的提示符格式。
- PS2 控制第二层命令行的提示符格式。
[mozhiyan@localhost ~]$ echo $PS1
[\u@\h \W]\$
[mozhiyan@localhost ~]$ echo $PS2
>
[mozhiyan@localhost ~]$
#!/bin/bash
#!是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,或在执行时,
/bin/bash test.sh #使用Bash的绝对路径
echo "Hello World !" #这是一条语句
chmod 755 filename.sh
./filename.sh
执行shell脚本$$
变量就可以获取当前进程的 PID。source filename 或者 . filename
两种写法的效果相同。对于第二种写法,注意点号.和文件名中间有一个空格。在 Bash shell 中,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储
**定义变量**
variable=value
variable='value'
variable="value"
赋值号=的周围不能有空格
变量名由数字、字母、下划线组成;
必须以字母或者下划线开头;
不能使用 Shell 里的关键字(通过 help 命令可以查看保留关键字)。
url=http://c.biancheng.net/shell/
echo $url
name='C语言中文网'
echo $name
author="严长生"
echo ${author}
变量名外面的花括号{ }是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界
**修改变量的值 **
url="http://c.biancheng.net"
echo ${url}
url="http://c.biancheng.net/shell/"
echo ${url}
单双引号区别
#!/bin/bash
url="http://c.biancheng.net"
website1='C语言中文网:${url}'
website2="C语言中文网:${url}"
echo $website1 >> C语言中文网:${url}
echo $website2 >> C语言中文网:http://c.biancheng.net
以单引号' '包围变量的值时,单引号里面是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出
以双引号" "包围变量的值时,输出时会先解析里面的变量和命令,而不是把双引号中的变量名和命令原样输出
将命令的结果赋值给变量
variable=`command`
variable=$(command)
只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变,类似于其他语言的常量
#!/bin/bash
myUrl="http://c.biancheng.net/shell/"
readonly myUrl
myUrl="http://c.biancheng.net/shell/"
删除变量
使用 unset 命令可以删除变量。语法:unset variable_name
- 有的变量可以在***当前 Shell 会话***中使用,这叫做全局变量(global variable);
- 有的变量只能在函数内部使用,这叫做局部变量(local variable);
- 而有的变量还可以在其它 Shell 中使用,这叫做环境变量(environment variable)
全局变量:
一般定义的变量均为全局变量
局部变量:
要想变量的作用域仅限于函数内部,那么可以在定义时加上local命令,此时该变量就成了局部变量
#!/bin/bash
#定义函数
function func(){
local a=99
}
#调用函数
func
#输出函数内部的变量
echo $a
环境变量:
如果使用export
命令将它导出,那么它就在所有的子 Shell 中也有效了,这称为“环境变量”
环境变量被创建时所处的 Shell 被称为父 Shell,如果在父 Shell 中再创建一个 Shell,则该 Shell 被称作子 Shell。当子 Shell 产生时,它会继承父 Shell 的环境变量为自己所用,所以说环境变量可从父 Shell 传给子 Shell。不难理解,环境变量还可以传递给孙 Shell
注意,环境变量只能向下传递而不能向上传递,即“传子不传父”。
export a
这种形式是在定义变量 a 以后再将它导出为环境变量,如果想在定义的同时导出为环境变量,可以写作export a=22
$n
的形式来接收的参数,在 Shell 中称为位置参数。给脚本文件传递位置参数
下面的代码命名为 test.sh:
#!/bin/bash
echo "Language: $1"
echo "URL: $2"
运行 test.sh,并附带参数:
. ./test.sh Shell http://c.biancheng.net/shell/
>> Language: Shell
>>URL: http://c.biancheng.net/shell/
给函数传递位置参数
编写下面的代码,并命名为 test.sh:
#!/bin/bash
#定义函数
function func(){
echo "Language: $1"
echo "URL: $2"
}
#调用函数
func C++ http://c.biancheng.net/cplus/
运行 test.sh:
. ./test.sh
>>Language: C++
>>URL: http://c.biancheng.net/cplus/
变量 | 含义 |
---|---|
$0 | 当前脚本的文件名。 |
$n(n≥1) | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2。 |
$# | 传递给脚本或函数的参数个数。 |
$* | 传递给脚本或函数的所有参数。 |
$@ | 传递给脚本或函数的所有参数。当被双引号" "包含时,$@ 与 $* 稍有不同 |
$? | 上个命令的退出状态,或函数的返回值,我们将在《Shell $?》一节中详细讲解。 |
$$ | 当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID。 |
$ $@ 区别*
$*
和 $@
不被双引号" "
包围时,它们之间没有任何区别,都是将接收到的每个参数看做一份数据,彼此之间以空格来分隔。
"$*"
会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据"$@"
仍然将每个参数都看作一份数据,彼此之间是独立的
编写下面的代码,并保存为 test.sh:
#!/bin/bash
echo "print each param from \"\$*\""
for var in "$*"
do
echo "$var"
done
echo "print each param from \"\$@\""
for var in "$@"
do
echo "$var"
done
运行 test.sh,并附带参数:
. ./test.sh a b c d
print each param from "$*"
a b c d
print each param from "$@"
a
b
c
d
对于"$*",只循环了 1 次,因为它只有 1 分数据;对于"$@",循环了 5 次,因为它有 5 份数据。
退出状态:
所谓退出状态,就是上一个命令执行后的返回结果。退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败返回 1
编写下面的代码,并保存为 test.sh:
#!/bin/bash
if [ "$1" == 100 ]
then
exit 0 #参数正确,退出状态为0
else
exit 1 #参数错误,退出状态1
fi
运行 test.sh 时传递参数 100:
./test.sh 100 #作为一个新进程运行
echo $?
>>0
再如,运行 test.sh 时传递参数 89:
./test.sh 89 #作为一个新进程运行
echo $?
>>1
获取函数的返回值
编写下面的代码,并保存为 test.sh:
#!/bin/bash
#得到两个数相加的和
function add(){
return `expr $1 + $2`
}
add 23 50 #调用函数
echo $? #获取函数返回值
运行结果:
73
严格来说,Shell 函数中的 return 关键字用来表示函数的退出状态,而不是函数的返回值
三种形式字符串:
' '
包围的字符串:
- 任何字符都会原样输出,在其中使用变量是无效的。
- 字符串中不能出现单引号,即使对单引号进行转义也不行。
" "
包围的字符串:
- 如果其中包含了某个变量,那么该变量会被解析(得到该变量的值),而不是原样输出。
- 字符串中可以出现双引号,只要它被转义了就行。
- 不被引号包裹的字符串中出现变量时也会被解析,与双引号
" "
包裹的字串一样- 字符串中不能出现空格,否则空格后面的字符串会作为其他变量或命令解析
#!/bin/bash
n=74
str1=c.biancheng.net$n
str2="shell \"script\" $n"
str3='C语言中文网 $n'
echo $str1
echo $str2
echo $str3
运行结果:
c.biancheng.net74
shell "script" 74
C语言中文网 $n
${#string_name}
string_name 表示字符串名字。
#!/bin/bash
str="http://c.biancheng.net/shell/"
echo ${#str}
运行结果:
29
在 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
从字符串左边开始计数
${string: start :length}
string 是要截取的字符串,start 是起始位置(从左边开始,从 0 开始计数),length 是要截取的长度(省略的话表示直到字符串的末尾)。
url=“c.biancheng.net”
echo ${url: 2: 9}
结果为biancheng
从右边开始计数
${string: 0-start :length}
第 2) 种格式仅仅多了0-,这是固定的写法,专门用来表示从字符串右边开始计数。
不管从哪边开始计数,截取方向都是从左到右。
url="c.biancheng.net"
echo ${url: 0-13: 9}
结果为biancheng。从右边数,b是第 13 个字符。
url="c.biancheng.net"
echo ${url: 0-13} #省略 length,直接截取到字符串末尾
结果为biancheng.net
${string#*chars}
string 表示要截取的字符,chars 是指定的字符(或者子字符串),*是通配符的一种,表示任意长度的字符串。 *chars连起来使用的意思是:忽略左边的所有字符,直到遇见 chars(chars 不会被截取)。 url="http://c.biancheng.net/index.html"
echo ${url#*:}
结果为//c.biancheng.net/index.html。
以上写法遇到第一个匹配的字符(子字符串)就结束了。例如:
url="http://c.biancheng.net/index.html"
echo ${url#*/}
结果为/c.biancheng.net/index.html
如果希望直到最后一个指定字符(子字符串)再匹配结束,那么可以使用##
: ${string##*chars}
#!/bin/bash
url="http://c.biancheng.net/index.html"
echo ${url#*/} #结果为 /c.biancheng.net/index.html
echo ${url##*/} #结果为 index.html
str="---aa+++aa@@@"
echo ${str#*aa} #结果为 +++aa@@@
echo ${str##*aa} #结果为 @@@
使用 % 截取左边字符
使用%号可以截取指定字符(或者子字符串)左边的所有字符${string%chars*}
#!/bin/bash
url="http://c.biancheng.net/index.html"
echo ${url%/*} #结果为 http://c.biancheng.net
echo ${url%%/*} #结果为 http:
str="---aa+++aa@@@"
echo ${str%aa*} #结果为 ---aa+++
echo ${str%%aa*} #结果为 ---
汇总
格式 | 说明 |
---|---|
${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 左边的所有字符。 |
常用的 Bash Shell 只支持一维数组,不支持多维数组。Shell 数组元素的下标也是从 0 开始计数。
数组定义 :array_name=(ele1 ele2 ele3 ... elen)
,元素空格隔开 注意,赋值号=
两边不能有空格,必须紧挨着数组名和数组元素
nums=(29 100 13 8 91 44)
3
#Shell 是弱类型的,它并不要求所有数组元素的类型必须相同:
arr=(20 56 "http://c.biancheng.net/shell/")
#Shell 数组的长度不是固定的,定义之后还可以增加元素。
nums[6]=88
#无需逐个元素地给数组赋值,下面的代码就是只给特定元素赋值:
ages=([3]=24 [5]=19 [10]=12)
#以上代码就只给第 3、5、10 个元素赋值,所以数组长度是 3。
获取数组元素:
#!/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[*]}
#!/bin/bash
nums=(29 100 13)
echo ${#nums[*]}
#向数组中添加元素
nums[10]="http://c.biancheng.net/shell/"
echo ${#nums[@]}
echo ${#nums[10]}
#删除数组元素
unset nums[1]
echo ${#nums[*]}
运行结果:
3
4
29
3
数组拼接,数组合并
拼接数组的思路是:先利用@或*,将数组扩展成列表,然后再合并到一起。
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]
其中,array_name 表示数组名,index 表示数组下标。
删除整个数组:unset array_name
#!/bin/bash
arr=(23 56 99 "http://c.biancheng.net/shell/")
unset arr[1]
echo ${arr[@]}
unset arr
echo ${arr[*]}
运行结果:
>> 23 99 http://c.biancheng.net/shell/
>>
关联数组
关联数组也称为“键值对(key-value)”数组,键(key)也即字符串形式的数组下标,值(value)也即元素值。
declare -A color
color["red"]="#ff0000"
color["green"]="#00ff00"
color["blue"]="#0000ff"
#也可以在定义的同时赋值:
declare -A color=(["red"]="#ff0000", ["green"]="#00ff00", ["blue"]="#0000ff")
关联数组必须使用带有-A选项的 declare 命令创建
访问关联数组元素:array_name["index"]
color["white"]="#ffffff"
color["black"]="#000000"
#加上$()即可获取数组元素的值:
$(array_name["index"])
echo $(color["white"])
white=$(color["black"])
示例:
#!/bin/bash
declare -A color
color["red"]="#ff0000"
color["green"]="#00ff00"
color["blue"]="#0000ff"
color["white"]="#ffffff"
color["black"]="#000000"
#获取所有元素值
for value in ${color[*]}
do
echo $value
done
echo "****************"
#获取所有元素下标(键)
for key in ${!color[*]}
do
echo $key
done
echo "****************"
#列出所有键值对
for key in ${!color[@]}
do
echo "${key} -> ${color[$key]}"
done
运行结果:
#ff0000
#0000ff
#ffffff
#000000
#00ff00
****************
red
blue
white
black
green
****************
red -> #ff0000
blue -> #0000ff
white -> #ffffff
black -> #000000
green -> #00ff00
.bashrc其实也是一个 Shell 脚本文件,该文件专门用来存放用户自定义的别名和函数。
#将别名写入.bashrc文件后的效果如下所示:
# .bashrc
# User specific aliases and functions
alias myShutdown='shutdown -h now'
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
unalias:删除别名
输出字符串
echo 命令输出结束后默认会换行,如果不希望换行,可以加上-n参数
#!/bin/bash
name="Tom"
age=20
height=175
weight=62
echo -n "${name} is ${age} years old, "
echo -n "${height}cm in height "
echo "and ${weight}kg in weight."
echo "Thank you!"
#运行结果:
Tom is 20 years old, 175cm in height and 62kg in weight.
Thank you!
输出转义字符
默认情况下,echo 不会解析以反斜杠\开头的转义字符,添加-e
参数来让 echo 命令解析转义字符
echo -e "hello \nworld"
hello
world
有了-e参数,我们也可以使用转义字符\c来强制 echo 命令不换行了
#!/bin/bash
name="Tom"
age=20
height=175
weight=62
echo -e "${name} is ${age} years old, \c"
echo -e "${height}cm in height \c"
echo "and ${weight}kg in weight."
echo "Thank you!"
#运行结果:
Tom is 20 years old, 175cm in height and 62kg in weight.
Thank you!
用来从标准输入中读取数据并赋值给变量,如果没有进行重定向,默认就是从键盘读取用户输入的数据;如果进行了重定向,那么可以从文件中读取数据
read [-options] [variables]
options表示选项,如下表所示;variables表示用来存储数据的变量,可以有一个,也可以有多个。
选项 | 说明 |
---|---|
-a array | 把读取的数据赋值给数组 array,从下标 0 开始。 |
-d delimiter | 用字符串 delimiter 指定读取结束的位置,而不是一个换行符(读取到的数据不包括 delimiter)。 |
-e | 在获取用户输入的时候,对功能键进行编码转换,不会直接显式功能键对应的字符。 |
-n num | 读取 num 个字符,而不是整行字符。 |
-p prompt | 显示提示信息,提示内容为 prompt。 |
-r | 原样读取(Raw mode),不把反斜杠字符解释为转义字符。 |
-s | 静默模式(Silent mode),不会在屏幕上显示输入的字符。当输入密码和其它确认信息的时候,这是很有必要的。 |
-t seconds | 设置超时时间,单位为秒。如果用户没有在指定时间内输入完成,那么 read |
-u fd | 使用文件描述符 fd 作为输入源,而不是标准输入,类似于重定向。 |
使用 read 命令给多个变量赋值:
#!/bin/bash
read -p "Enter some information > " name url age
echo "网站名字:$name"
echo "网址:$url"
echo "年龄:$age"
#运行结果:
Enter some information > C语言中文网 http://c.biancheng.net 7↙
网站名字:C语言中文网
网址:http://c.biancheng.net
年龄:7
只读取一个字符:
#!/bin/bash
read -n 1 -p "Enter a char > " char
printf "\n" #换行
echo $char
#运行结果:
Enter a char > 1
1
在指定时间内输入密码:
#!/bin/bash
if
read -t 20 -sp "Enter password in 20 seconds(once) > " pass1 && printf "\n" && #第一次输入密码
read -t 20 -sp "Enter password in 20 seconds(again)> " pass2 && printf "\n" && #第二次输入密码
[ $pass1 == $pass2 ] #判断两次输入的密码是否相等
then
echo "Valid password"
else
echo "Invalid password"
fi
# 使用&&组合了多个命令
exit 命令可以接受一个整数值作为参数,代表退出状态。如果不指定,默认状态值是 0。
一般情况下,退出状态为 0 表示成功,退出状态为非 0 表示执行失败(出错)了。
exit 退出状态只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
#!/bin/bash
echo "befor exit"
exit 8
echo "after exit"
./test.sh
befor exit
echo $? #使用$?来获取 test.sh 的退出状态:
8
设置变量属性 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
将变量定义为只读变量:
declare -r n=10
n=20
>>bash: n: 只读变量
echo $n
>>10
显示变量的属性和值:
declare -r n=10
declare -p n
>>declare -r n="10"
###命令替换
将命令的输出结果赋值给变量
有两种方式可以完成命令替换,一种是反引号
,一种是$(),使用方法如下:
var_name=`command`
var_name=$(command)
#!/bin/bash
DATE_01=`date`
DATE_02=$(date)
echo $DATE_01
echo $DATE_02
#运行结果:
2018年 10月 31日 星期三 10:15:16 CST
2018年 10月 31日 星期三 10:15:16 CST
如果被替换的命令的输出内容包括多行(也即有换行符),或者含有多个连续的空白符,那么在输出变量时应该将变量用双引号包围
#!/bin/bash
LSL=`ls -l`
echo $LSL #不使用双引号包围
echo "--------------------------" #输出分隔符
echo "$LSL" #使用引号包围
运行结果:
total 8 drwxr-xr-x. 2 root root 21 7月 1 2016 abc -rw-rw-r--. 1 mozhiyan mozhiyan 147 10月 31 10:29 demo.sh -rw-rw-r--. 1 mozhiyan mozhiyan 35 10月 31 10:20 demo.sh~
--------------------------
total 8
drwxr-xr-x. 2 root root 21 7月 1 2016 abc
-rw-rw-r--. 1 mozhiyan mozhiyan 147 10月 31 10:29 demo.sh
-rw-rw-r--. 1 mozhiyan mozhiyan 35 10月 31 10:20 demo.sh~
算术运算符 | 说明/含义 |
---|---|
+、- | 加法(或正号)、减法(或负号) |
*、/、% | 乘法、除法、取余(取模) |
** | 幂运算 |
++、– | 自增和自减,可以放在变量的前面也可以放在变量的后面 |
!、&&、 | |
<、<=、>、>= | 比较符号(小于、小于等于、大于、大于等于) |
==、!=、= | 比较符号(相等、不相等;对于字符串,= 也可以表示相当于) |
<<、>> | 向左移位、向右移位 |
~、 | 、 &、^ |
=、+=、-=、*=、/=、%= | 赋值运算符,例如 a+=1 相当于 a=a+1,a-=1 相当于 a=a-1 |
Shell 不能直接进行算数运算,必须使用数学计算命令,默认情况下,Shell 不会直接进行算术运算
***学习 (()) 和 bc 即可
运算操作符/运算命令 | 说明 |
---|---|
(( )) | 用于整数运算,效率很高,推荐使用。 |
let | 用于整数运算,和 (()) 类似。 |
$[] | 用于整数运算,不如 (()) 灵活。 |
expr | 可用于整数运算,也可以处理字符串。比较麻烦,需要注意各种细节,不推荐使用。 |
bc | Linux下的一个计算器程序,可以处理整数和小数。Shell 本身只支持整数运算,想计算小数就得使用 bc 这个外部的计算器。 |
declare -i | 将变量定义为整数,然后再进行数学运算时就不会被当做字符串了。功能有限,仅支持最基本的数学运算(加减乘除和取余),不支持逻辑运算、自增自减等,所以在实际开发中很少使用。 |
表达式可以只有一个,也可以有多个,多个表达式之间以逗号,
分隔。对于多个表达式的情况,以最后一个表达式的值作为整个(( ))
命令的执行结果
运算操作符/运算命令 | 说明 |
---|---|
((a=10+66) ((b=a-15)) ((c=a+b)) | 这种写法可以在计算完成后给变量赋值。以((b=a-15)) 为例,即将 a-15 的运算结果赋值给变量 c。 注意,使用变量时不用加$前缀,(( )) 会自动解析变量名。 |
a= ( ( 10 + 66 ) b = ((10+66) b= ((10+66)b=((a-15)) c=$((a+b)) | 可以在 (( )) 前面加上 符 号 获 取 ( ( ) ) 命 令 的 执 行 结 果 , 也 即 获 取 整 个 表 达 式 的 值 。 以 c = 符号获取 (( )) 命令的执行结果,也即获取整个表达式的值。以 c= 符号获取(())命令的执行结果,也即获取整个表达式的值。以c=((a+b)) 为例,即将 a+b 这个表达式的运算结果赋值给变量 c。 注意,类似 c=((a+b)) 这样的写法是错误的,不加$就不能取得表达式的结果。 |
((a>7 && b==c)) | (( )) 也可以进行逻辑运算,在 if 语句中常会使用逻辑运算。 |
echo $((a+10)) | 需要立即输出表达式的运算结果时,可以在 (( )) 前面加$符号。 |
((a=3+5, b=a+10)) | 对多个表达式同时进行计算。 |
基本复杂运算:
((a=1+2**3-4%3))
echo $a
>>8
b=$((1+2**3-4%3)) #运算后将结果赋值给变量,变量放在了括号的外面。
echo $b
>>8
echo $((1+2**3-4%3)) #也可以直接将表达式的结果输出,注意不要丢掉 $ 符号。
>>8
a=$((100*(100+1)/2)) #利用公式计算1+2+3+...+100的和。
echo $a
>>5050
echo $((100*(100+1)/2)) #也可以直接输出表达式的结果。
5050
利用(())
进行逻辑运算:
echo $((3<8)) #3<8 的结果是成立的,因此,输出了 1,1 表示真
>>1
echo $((8<3)) #8<3 的结果是不成立的,因此,输出了 0,0 表示假。
>>0
echo $((8==8)) #判断是否相等。
>>1
if ((8>7&&5==5))
> then
> echo yes
> fi
yes
利用 (( )) 同时对多个表达式进行计算:
((a=3+5, b=a+10)) #先计算第一个表达式,再计算第二个表达式
echo $a $b
8 18
c=$((4+8, a+b)) #以最后一个表达式的结果作为整个(())命令的执行结果
echo $c
26
只能对整数进行计算
let 表达式
或 let "表达式"
或 let '表达式'
=> 都等价于((表达式))
let 命令以空格来分隔多个表达式;
(( )) 以逗号,来分隔多个表达式。
a=10 b=20
echo $((a+b))
>>30
echo let a+b #错误,echo会把 let a+b作为一个字符串输出
>>let a+b
#给变量 i 加 8:
i=2
let i+=8
echo $i
>>10
let 后面可以跟多个表达式:
a=10 b=35
let a+=6 c=a+b #多个表达式以空格为分隔
echo $a $c
>>16 51
$[] 也只能进行整数运算
echo $[3*5] #直接输出结算结果
>>15
echo $[(3+4)*5] #使用()
35
n=6
m=$[n*2] #将计算结果赋值给变量
echo $[m+n]
>>18
echo $[$m*$n] #在变量前边加$也是可以的
>>72
注意的是,不能单独使用 $[],必须能够接收 $[] 的计算结果:
$[3+4]
>>bash: 7: 未找到命令...
$[m+3]
bash: 15: 未找到命令...
除了可以实现整数计算,还可以结合一些选项对字符串进行处理,例如计算字符串长度、字符串比较、字符串匹配、字符串提取等
expr
对表达式的格式有几点特殊的要求:
出现在表达式中的运算符、数字、变量和小括号的左右两边至少要有一个空格,否则会报错。
有些特殊符号必须用反斜杠\进行转义(屏蔽其特殊含义),比如乘号*
和小括号()
,如果不用\转义,那么 Shell 会把它们误解为正则表达式中的符号(*
对应通配符,()
对应分组)。
使用变量时要加$前缀。
expr 2 +3 #错误:加号和 3 之前没有空格
>>expr: 语法错误
expr 2 + 3 #这样才是正确的
>>5
expr 4 * 5 #错误:乘号没有转义
>>expr: 语法错误
expr 4 \* 5 #使用 \ 转义后才是正确的
>>20
expr ( 2 + 3 ) \* 4 #小括号也需要转义
>>bash: 未预期的符号 `2' 附近有语法错误
expr \( 2 + 3 \) \* 4 #使用 \ 转义后才是正确的
>>20
n=3
expr n + 2 #使用变量时要加 $
>>expr: 非整数参数
expr $n + 2 #加上 $ 才是正确的
>>5
m=7
expr $m \* \( $n + 5 \)
>>56
如果希望将计算结果赋值给变量,那么需要将整个表达式用反引号``(位于 Tab 键的上方)包围起来
将 expr 的计算结果赋值给变量:
m=5
n=`expr $m + 10`
echo $n
>>15
交互式:
在终端输入bc
命令,然后回车即可进入bc
进行交互式的数学计算,bc -q
清爽显示
bc 还支持函数、循环结构、分支结构等常见的编程元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PdthOxNy-1597903870361)(./res/bc_function.png)]
变量名 | 作 用 |
---|---|
scale | 指定精度,也即小数点后的位数;默认为 0,也即不使用小数部分。 |
ibase | 指定输入的数字的进制,默认为十进制。 |
obase | 指定输出的数字的进制,默认为十进制。 |
last 或者 . | 表示最近打印的数字 |
bc -q
23*2
46
obase=16
23*2
2E
obase=10
ibase=16
10*10
256
在一行中使用多个表达式:
Shell 中使用 bc
在 Shell 脚本中,我们可以借助管道或者输入重定向来使用 bc 计算器
|
分隔借助管道使用 bc 计算器:
echo "expression" | bc
将 bc 的计算结果赋值给 Shell 变量
variable=$(echo "expression" | bc)
echo "3*8"|bc
>>24
ret=$(echo "4+9"|bc)
echo $ret
>>13
echo "scale=4;3*8/7"|bc
>>3.4285
echo "scale=4;3*8/7;last*5"|bc
>>3.4285
>>17.1425
x=4
echo "scale=5;n=$x+2;e(n)"|bc -l
>>403.42879
借助输入重定向使用 bc 计算器
variable=$(bc << EOF
expressions
EOF
)
variable
是 Shell 变量名,express
是要计算的数学表达式(可以换行,和进入 bc 以后的书写形式一样),EOF
是数学表达式的开始和结束标识(你也可以换成其它的名字,比如 aaa、bbb 等)。
m=1E
n=$(bc << EOF
> obase=10;
> ibase=16;
> print $m
> EOF
> )
echo $n
>>30
#!/bin/bash
declare -i m n ret
m=10
n=30
ret=$m+$n
echo $ret
ret=$n/$m
echo $ret
除了将参与运算的变量定义为整数,还得将承载结果的变量定义为整数,而且只能用整数类型的变量来承载运算结果,不能直接使用echo
输出
if condition
then
statement(s)
fi
condition
是判断条件,如果condition
成立(返回“真”),那么then
后边的语句将会被执行;如果condition
不成立(返回“假”),那么不会执行任何语句
#!/bin/bash
read a
read b
if (( $a == $b ))
then
echo "a和b相等"
fi
if else
if condition
then
statement1
else
statement2
fi
example:
#!/bin/bash
read a
read b
if (( $a == $b ))
then
echo "a和b相等"
else
echo "a和b不相等,输入错误"
fi
if elif else
if condition1
then
statement1
elif condition2
then
statement2
elif condition3
then
statement3
……
else
statementn
fi
if 和 elif 后边都得跟着 then。
#!/bin/bash
read age
if (( $age <= 2 )); then
echo "婴儿"
elif (( $age >= 3 && $age <= 8 )); then
echo "幼儿"
elif (( $age >= 9 && $age <= 17 )); then
echo "少年"
elif (( $age >= 18 && $age <=25 )); then
echo "成年"
elif (( $age >= 26 && $age <= 40 )); then
echo "青年"
elif (( $age >= 41 && $age <= 60 )); then
echo "中年"
else
echo "老年"
fi
退出状态为 0 表示“成功”,为真;也就是说,程序执行完成并且没有遇到任何问题。除 0 以外的其它任何退出状态都为“失败”,为假。
退出状态和逻辑运算符的组合
运算符 | 使用格式 | 说明 |
---|---|---|
&& |
expression1 && expression2 |
逻辑与运算符,当 expression1 和 expression2 同时成立时,整个表达式才成立。 |
|| |
expression1 || expression2 |
逻辑或运算符,expression1 和 expression2 两个表达式中只要有一个成立,整个表达式就成立。 |
! | !expression |
逻辑非运算符,相当于“取反”的效果。如果expression 成立,那么整个表达式就不成立;如果expression 不成立,那么整个表达式就成立。 |
用来检测某个条件是否成立
test expression
或者 [ expression ]
,注意[]
和expression
之间的空格,这两个空格是必须的,否则会导致语法错误
#!/bin/bash
read age
if test $age -le 2; then
echo "婴儿"
elif test $age -ge 3 && test $age -le 8; then
echo "幼儿"
elif [ $age -ge 9 ] && [ $age -le 17 ]; then
echo "少年"
elif [ $age -ge 18 ] && [ $age -le 25 ]; then
echo "成年"
elif test $age -ge 26 && test $age -le 40; then
echo "青年"
elif test $age -ge 41 && [ $age -le 60 ]; then
echo "中年"
else
echo "老年"
fi
-le
选项表示小于等于,-ge
选项表示大于等于,&&
是逻辑与运算符。
重点是学习test
的各种选项,具体参考
在test
命令中使用变量时,我强烈建议将变量用双引号""
包围起来,这样能避免变量为空值时导致的很多奇葩问题
[[ expression ]]
当[[ ]]
判断 expression
成立时,退出状态为 0,否则为非 0 值。注意[[ ]]
和expression
之间的空格,这两个空格是必须的,否则会导致语法错误
#!/bin/bash
read str1
read str2
if [[ -z $str1 ]] || [[ -z $str2 ]] #不需要对变量名加双引号
then
echo "字符串不能为空"
elif [[ $str1 < $str2 ]] #不需要也不能对 < 进行转义
then
echo "str1 < str2"
else
echo "str1 >= str2"
fi
#可以使用逻辑运算符将多个 test 命令连接起来
[ -z "$str1" ] || [ -z "$str2" ]
[ -z "$str1" -o -z "$str2" ]
[[ -z $str1 || -z $str2 ]]
test 或 [] | [[ ]] |
---|---|
[ -z "$str1" ] || [ -z "$str2" ] √ |
[[ -z $str1 ]] ||[[ -z $str2 ]] √ |
[ -z "$str1" -o -z "$str2" ] √ |
[[ -z $str1 -o -z$str2 ]] × |
[[ -z $str1 || -z $str2 ]] × |
[[ -z $str1 || -z $str2 ]] √ |
[[ ]] 支持正则表达式
[[ str =~ regex ]]
#!/bin/bash
read str1
read str2
if [[ -z $str1 ]] || [[ -z $str2 ]] #不需要对变量名加双引号
then
echo "字符串不能为空"
elif [[ $str1 < $str2 ]] #不需要也不能对 < 进行转义
then
echo "str1 < str2"
else
echo "str1 >= str2"
fi
case expression in
pattern1)
statement1
;;
pattern2)
statement2
;;
pattern3)
statement3
;;
……
*)
statementn
esac
例子:
#!/bin/bash
printf "Input integer number: "
read num
case $num in
1)
echo "Monday"
;;
2)
echo "Tuesday"
;;
3)
echo "Wednesday"
;;
4)
echo "Thursday"
;;
5)
echo "Friday"
;;
6)
echo "Saturday"
;;
7)
echo "Sunday"
;;
*)
echo "error"
esac
- expression 既可以是一个变量、一个数字、一个字符串,还可以是一个数学计算表达式,或者是命令的执行结果,只要能够得到 expression 的值就可以。
- pattern 可以是一个数字、一个字符串,甚至是一个简单的正则表达式。
case
会将expression
的值与pattern1、pattern2、pattern3
逐个进行匹配:
- 如果
expression
和某个模式(比如pattern2
)匹配成功,就会执行这模式(比如pattern2
)后面对应的所有语句(该语句可以有一条,也可以有多条),直到遇见双分号;;
才停止;然后整个case
语句就执行完了,程序会跳出整个case
语句,执行esac
后面的其它语句。- 如果
expression
没有匹配到任何一个模式,那么就执行*)
后面的语句(*
表示其它所有值),直到遇见双分号;;
或者esac
才结束。*)
相当于多个if
分支语句中最后的else
部分。Shell case in
语句中的*)
用来“托底”,万一expression
没有匹配到任何一个模式,*)
部分可以做一些“善后”工作,或者给用户一些提示。- 可以没有
*)
部分。如果expression
没有匹配到任何一个模式,那么就不执行任何操作。- 除最后一个分支外(这个分支可以是普通分支,也可以是
*)
分支),其它的每个分支都必须以;;
结尾,;;
代表一个分支的结束,不写的话会有语法错误。最后一个分支可以写;;
,也可以不写,因为无论如何,执行到esac
都会结束整个case in
语句
case in 和正则表达式
格式 | 说明 |
---|---|
* |
表示任意字符串。 |
[abc] |
表示 a、b、c 三个字符中的任意一个。比如,[15ZH] 表示 1、5、Z、H 四个字符中的任意一个。 |
[m-n] |
表示从 m 到 n 的任意一个字符。比如,[0-9] 表示任意一个数字,[0-9a-zA-Z] 表示字母或数字。 |
| |
表示多重选择,类似逻辑运算中的或运算。比如,abc |
最后一个分支*)
并不是什么语法规定,它只是一个正则表达式,*
表示任意字符串,所以不管 expression
的值是什么,*)
总能匹配成功。
#!/bin/bash
printf "Input a character: "
read -n 1 char
case $char in
[a-zA-Z])
printf "\nletter\n"
;;
[0-9])
printf "\nDigit\n"
;;
[0-9])
printf "\nDigit\n"
;;
[,.?!])
printf "\nPunctuation\n"
;;
*)
printf "\nerror\n"
esac
while 循环的用法如下:
while condition
do
statements
done
在 while 循环体中必须有相应的语句使得 condition 越来越趋近于“不成立”,只有这样才能最终退出循环,否则 while 就成了死循环,会一直执行下去,永无休止。
#!/bin/bash
i=1
sum=0
while ((i <= 100))
do
((sum += i))
((i++))
done
echo "The sum is: $sum"
until 循环的用法如下:
until condition
do
statements
done
until 循环的执行流程为:
先对 condition 进行判断,如果该条件不成立,就进入循环,执行 until 循环体中的语句(do 和 done 之间的语句),这样就完成了一次循环。
每一次执行到 done 的时候都会重新判断 condition 是否成立,如果不成立,就进入下一次循环,继续执行循环体中的语句,如果成立,就结束整个 until 循环,执行 done 后面的其它 Shell 代码。
如果一开始 condition 就成立,那么程序就不会进入循环体,do 和 done 之间的语句就没有执行的机会。
#!/bin/bash
i=1
sum=0
until ((i > 100))
do
((sum += i))
((i++))
done
echo "The sum is: $sum"
Shell for循环有两种使用形式
C语言风格的 for
循环
for((exp1; exp2; exp3))
do
statements
done
for 循环的执行过程
#!/bin/bash
sum=0
for ((i=1; i<=100; i++))
do
((sum += i))
done
echo "The sum is: $sum"
for
循环中的 exp1
(初始化语句)、exp2
(判断条件)和 exp3
(自增或自减)都是可选项,都可以省略(但分号;
必须保留)
在循环体内部使用break
关键字强制结束循环:
Python 风格的 for in
循环
for variable in value_list
do
statements
done
variable
表示变量,value_list
表示取值列表,in
是 Shell
中的关键字。
#!/bin/bash
sum=0
for n in 1 2 3 4 5 6
do
echo $n
((sum+=n))
done
echo "The sum is "$sum
value_list
的说明:
- 直接给出具体的值
可以在 in 关键字后面直接给出具体的值
#!/bin/bash
for str in "C语言中文网" "http://c.biancheng.net/" "成立7年了" "日IP数万"
do
echo $str
done
- 给出一个取值范围
可以在 in 关键字后面给出范围:
{start..end}
,start
表示起始值,end
表示终止值;注意中间用两个点号
相连,而不是三个点号,这种形式只支持数字和字母
。
#计算从 1 加到 100 的和:
#!/bin/bash
sum=0
for n in {1..100}
do
((sum+=n))
done
echo $sum
#输出从 A 到 z 之间的所有字符:
#!/bin/bash
for c in {A..z}
do
printf "%c" $c
done
- 使用命令的执行结果
使用反引号’'或者
$()
都可以取得命令的执行结果
#!/bin/bash
sum=0
for n in $(seq 2 2 100)
do
((sum+=n))
done
echo $sum
#列出当前目录下的所有 Shell 脚本文件:
#!/bin/bash
for filename in $(ls *.sh)
do
echo $filename
done
- 使用 Shell 通配符
Shell 通配符可以认为是一种精简化的正则表达式,通常用来匹配目录或者文件,而不是文本
#!/bin/bash
for filename in *.sh
do
echo $filename
done
- 使用特殊变量
Shell 中有多个特殊的变量,例如 KaTeX parse error: Expected 'EOF', got '#' at position 1: #̲、*、 @ 、 @、 @、?、$$ 等
#!/bin/bash
function func(){
for str in $@
do
echo $str
done
}
也可以省略 value_list
,省略后的效果和使用$@
一样
select in
循环用来增强交互性,它可以显示出带编号的菜单,用户输入不同的编号就可以选择不同的菜单,并执行不同的功能。
Shell select in
循环的用法如下:
select variable in value_list
do
statements
done
variable
表示变量,value_list
表示取值列表,in
是 Shell 中的关键字
#!/bin/bash
echo "What is your favourite OS?"
select name in "Linux" "Windows" "Mac OS" "UNIX" "Android"
do
echo $name
done
echo "You have selected $name"
运行结果:
What is your favourite OS?
1) Linux
2) Windows
3) Mac OS
4) UNIX
5) Android
#? 4↙
You have selected UNIX
#? 1↙
You have selected Linux
#? 9↙
You have selected
#? 2↙
You have selected Windows
#?^D
#?
用来提示用户输入菜单编号;^D
表示按下 Ctrl+D
组合键,它的作用是结束 select in
循环
运行到 select 语句后,取值列表 value_list
中的内容会以菜单的形式显示出来,用户输入菜单编号,就表示选中了某个值,这个值就会赋给变量 variable
,然后再执行循环体中的 statements
(do
和 done
之间的部分)。
select in
通常和 case in
一起使用,在用户输入不同的编号时可以做出不同的反应
#!/bin/bash
echo "What is your favourite OS?"
select name in "Linux" "Windows" "Mac OS" "UNIX" "Android"
do
case $name in
"Linux")
echo "Linux是一个类UNIX操作系统,它开源免费,运行在各种服务器设备和嵌入式设备。"
break
;;
"Windows")
echo "Windows是微软开发的个人电脑操作系统,它是闭源收费的。"
break
;;
"Mac OS")
echo "Mac OS是苹果公司基于UNIX开发的一款图形界面操作系统,只能运行与苹果提供的硬件之上。"
break
;;
"UNIX")
echo "UNIX是操作系统的开山鼻祖,现在已经逐渐退出历史舞台,只应用在特殊场合。"
break
;;
"Android")
echo "Android是由Google开发的手机操作系统,目前已经占据了70%的市场份额。"
break
;;
*)
echo "输入错误,请重新输入"
esac
done
break
和 continue
却能够跳出多层循环,也就是说,内层循环中的 break
和 continue
能够跳出外层循环Shell break 关键字的用法为:
break n
n 表示跳出循环的层数,如果省略 n,则表示跳出当前的整个循环
#可以在 break 后面跟一个数字,让它一次性地跳出两层循环
#!/bin/bash
i=0
while ((++i)); do #外层循环
j=0;
while ((++j)); do #内层循环
if((i>4)); then
break 2 #跳出内外两层循环
fi
if((j>4)); then
break #跳出内层循环
fi
printf "%-4d" $((i*j))
done
printf "\n"
done
Shell continue 关键字的用法为:
continue n
n 表示循环的层数:
使用 continue 跳出多层循环,请看下面的代码:
#!/bin/bash
for((i=1; i<=5; i++)); do
for((j=1; j<=5; j++)); do
if((i*j==12)); then
continue 2
fi
printf "%d*%d=%-4d" $i $j $((i*j))
done
printf "\n"
done
运行结果:
1*1=1 1*2=2 1*3=3 1*4=4 1*5=5
2*1=2 2*2=4 2*3=6 2*4=8 2*5=10
3*1=3 3*2=6 3*3=9 4*1=4 4*2=8 5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
从运行结果可以看出,遇到continue 2时,不但跳过了内层 for 循环,也跳过了外层 for 循环
Shell 函数定义的语法格式
function name() {
statements
[return value]
}
对各个部分的说明:
由{ }包围的部分称为函数体,调用一个函数,实际上就是执行函数体中的代码。其中,function
和 ()
均可省略任一个
- 调用 Shell 函数时可以给它传递参数,也可以不传递。如果不传递参数,直接给出函数名字即可:
name
- 如果传递参数,那么多个参数之间以空格分隔:
name param1 param2 param3
- 和其它编程语言不同的是,Shell 函数在定义时不能指明参数,但是在调用时却可以传递参数,并且给它传递什么参数它就接收什么参数。
#!/bin/bash
function getsum(){
local sum=0
for n in $@
do
((sum+=n))
done
return $sum
}
getsum 10 20 55 15 #调用函数并传递参数
echo $?
Shell 中的函数在定义时不能指明参数,但是在调用时却可以传递参数。
函数参数是 Shell 位置参数的一种,在函数内部可以使用$n
来接收,例如,$1
表示第一个参数,$2
表示第二个参数,依次类推。
$#
可以获取传递的参数的个数;
$@
或者$*
可以一次性获取所有的参数
使用 $n 来接收函数参数。
#!/bin/bash
#定义函数
function show(){
echo "Tutorial: $1"
echo "URL: $2"
echo "Author: "$3
echo "Total $# parameters"
}
#调用函数
show C# http://c.biancheng.net/csharp/ Tom
使用 $@ 来遍历函数参数。
#!/bin/bash
function getsum(){
local sum=0
for n in $@
do
((sum+=n))
done
echo $sum
return 0
}
#调用函数并传递参数,最后将结果赋值给一个变量
total=$(getsum 10 20 55 15)
echo $total
#也可以将变量省略
echo $(getsum 10 20 55 15)
Shell 函数的返回值只能是一个介于 0~255 之间的整数,其中只有 0 表示成功,其它值都表示失败。
得到函数的处理结果:
一种是借助全局变量,将得到的结果赋值给全局变量;
一种是在函数内部使用 echo
、printf
命令将结果输出,在函数外部使用$()
或者``捕获结果。
# 在函数内部使用 echo 输出结果
#!/bin/bash
function getsum(){
local sum=0 #局部变量
for((i=$1; i<=$2; i++)); do
((sum+=i))
done
echo $sum
return $?
}
read m
read n
total=$(getsum $m $n)
echo "The sum is $total"
#也可以省略 total 变量,直接写成下面的形式
#echo "The sum is "$(getsum $m $n)
$()
捕获了第一个 echo
的输出结果,它并没有真正输出到终端上。除了$()
,你也可以使用``来捕获 echo
的输出结果
Linux Shell 重定向分为两种,一种输入重定向,一种是输出重定向;从字面上理解,输入输出重定向就是「改变输入与输出的方向」
表1:与输入输出有关的文件描述符
文件描述符 | 文件名 | 类型 | 硬件 |
---|---|---|---|
0 | stdin | 标准输入文件 | 键盘 |
1 | stdout | 标准输出文件 | 显示器 |
2 | stderr | 标准错误输出文件 | 显示器 |
Linux Shell 输出重定向
Shell 输入重定向
输入重定向
符号 | 说明 |
---|---|
command 将 file 文件中的内容作为 command 的输入。 |
|
command <从标准输入(键盘)中读取数据,直到遇见分界符 END 才停止(分界符可以是任意的字符串,用户自己定义)。 |
|
command file2 | 将 file1 作为 command 的输入,并将 command 的处理结果输出到 file2。 |
Linuxwc
命令可以用来对文本进行统计,包括单词个数、行数、字节数,它的用法如下:
wc [选项] [文件名]
其中,-c
选项统计字节数,-w
选项统计单词数,-l
选项统计行数。
逐行读取文件内容。
#!/bin/bash
while read str; do
echo $str
done
source 是 Shell 内置命令的一种,会读取 filename
文件中的代码,并依次执行所有语句.
source 命令会强制执行脚本文件中的全部命令,而忽略脚本文件的权限。
source
命令的用法为:source filename
也可以简写为:. filename
func.sh 文件内容:
#计算所有参数的和
function sum(){
local total=0
for n in $@
do
((total+=n))
done
echo $total
return 0
}
main.sh 文件内容:
#!/bin/bash
source func.sh
echo $(sum 10 20 55 15)
source filename
与sh filename
及./filename
执行脚本的区别在那里呢?
sh filename
与./filename
执行脚本是没有区别得。./filename
是因为当前目录没有在PATH中,所有"."是用来表示当前目录的。sh filename
重新建立一个子shell,在子shell中执行脚本里面的语句,该子shell继承父shell的环境变量,但子shell新建的、改变的变量不会被带回父shell,除非使用export。source filename
:这个命令其实只是简单地读取脚本里面的语句依次在当前shell里面执行,没有建立新的子shell。那么脚本里面所有新建、改变变量的语句都会保存在当前shell里面。避免重复引入
Shell 脚本是从上至下、从左至右依次执行每一行的命令及语句的,即执行完一个命令之后再执行下一个。如果在 Shell 脚本中遇到子脚本(即脚本嵌套),就会先执行子脚本的内容,完成后再返回父脚本继续执行父脚本内后续的命令及语句。
如果以新进程的形式运行 Shell 脚本,会向系统内核请求启动一个新的进程,以便在该进程中执行脚本的命令及子 Shell 脚本,基本流程如下图所示:
Shell 组命令的写法有两种:
{ command1; command2; command3; . . . }
(command1; command2; command3;. . . )
两种写法的区别在于:
由花括号{}
包围起来的组命名在当前 Shell 进程中执行,而由小括号()
包围起来的组命令会创建一个子 Shell,所有命令都在子 Shell 中执行。
对于第一种写法,花括号和命令之间必须有一个空格,并且最后一个命令必须用一个分号或者一个换行符结束。
组命令可以将多条命令的输出结果合并在一起,在使用重定向和管道时会特别方便。
下面的代码将多个命令的输出重定向到 out.txt:
ls -l > out.txt #>表示覆盖
echo “http://c.biancheng.net/shell/” >> out.txt #>>表示追加
cat readme.txt >> out.txt
借助组命令,三条命令合并在一起,简化成一次重定向:
{ ls -l; echo “http://c.biancheng.net/shell/”; cat readme.txt; } > out.txt
(ls -l; echo “http://c.biancheng.net/shell/”; cat readme.txt) > out.txt
也可以将组命令和管道结合起来:
{ ls -l; echo “http://c.biancheng.net/shell/”; cat readme.txt; } | lpr
ls -l
或date_time=$(date);Shell 进程替换有两种写法:
一种用来产生标准输出,借助输入重定向,它的输出结果可以作为另一个命令的输入:
<(commands)
另一种用来接受标准输入,借助输出重定向,它可以接收另一个命令的输出结果:
>(commands)
commands
是一组命令列表,多个命令之间以分号;
分隔。注意,<
或>
与圆括号之间是没有空格的。
使用进程替换:
read < <(echo "http://c.biancheng.net/shell/")
echo $REPLY
整体上来看,Shell 把echo "http://c.biancheng.net/shell/"
的输出结果作为 read
的输入。<()
用来捕获 echo
命令的输出结果,<
用来将该结果重定向到 read
。
注意,两个<
之间是有空格的,第一个<
表示输入重定向,第二个<
和()
连在一起表示进程替换。
一个进程替换用作「接受标准输入」的例子:
echo "C语言中文网" > >(read; echo "你好,$REPLY")
运行结果:
你好,C语言中文网
进程替换本质:
#!/bin/bash
read newProcess< <(echo $$)
echo "newPID=$newProcess"
echo "thisPID=$$"
运行结果:
newPID=2681
thisPID=2681
两个进程的 PID 一样,进程替换并没有创建新的进程
实际上,进程替换会跟系统中的文件关联起来,这个文件的名字为/dev/fd/n
(n 是一个整数)。该文件会作为参数传递给()
中的命令,()
中的命令对该文件是读取还是写入取决于进程替换格式是<
还是>
:
>()
,那么该文件会给()
中的命令提供输入;借助输出重定向,要输入的内容可以从其它命令而来。<()
,那么该文件会接收()
中命令的输出结果;借助输入重定向,可以将该文件的内容作为其它命令的输入。使用 echo 命令可以查看进程替换对应的文件名:
[c.biancheng.net]$ echo >(true)
/dev/fd/63
[c.biancheng.net]$ echo <(true)
/dev/fd/63
[c.biancheng.net]$ echo >(true) <(true)
/dev/fd/63 /dev/fd/62
/dev/fd/目录下有很多序号文件,进程替换一般用的是 63 号文件,该文件是系统内部文件,我们一般查看不到。
我们通过下面的语句进行实例分析:
echo "shellscript" > >(read; echo "hello, $REPLY")
第一个>
表示输出重定向,它把第一个 echo
命令的输出结果重定向到/dev/fd/63
文件中。
/dev/fd/63
文件起到了数据中转或者数据桥梁的作用,借助重定向,它将>()
内部的命令和外部的命令联系起来,使得数据能够在这些命令之间流通。
- 打开文件表(Open file table)
- i-node 表(i-node table):i节点不仅包含了文件数据存储区的地址,还包含了很多信息,比如数据大小,等等文件信息。但是i节点是不保存文件名的。文件名是保存在一个目录项中。每一个目录项中都包含了文件名和i节点。
结合Linux文件描述符理解重定向:
Linux 系统每次读写文件的时候,都从文件描述符下手,通过文件描述符找到文件指针,然后进入打开文件表和 i-node 表,这两个表里面才真正保存了与打开文件相关的各种信息。
文件指针只不过是一个内存地址,修改它是轻而易举的事情。文件指针是文件描述符和真实文件之间最关键的“纽带”,然而这条纽带却非常脆弱,很容易被修改。
输入输出重定向就是通过修改文件指针实现的!
发生重定向时,Linux 会用文件描述符表(一个结构体数组)中的一个元素给另一个元素赋值,或者用一个结构体变量给数组元素赋值,整体上的资源开销相当低。
发生重定向的时候,文件描述符并没有改变,改变的是文件描述符对应的文件指针。对于标准输出,Linux 系统始终向文件描述符 1 中输出内容,而不管它的文件指针指向哪里;只要我们修改了文件指针,就能向任意文件中输出内容。
>
是输出重定向符号,<
是输入重定向符号;更准确地说,它们应该叫做文件描述符操作符。>
和 <
通过修改文件描述符改变了文件指针的指向,所以能够实现重定向的功能。
使用 exec 命令可以永久性地重定向,后续命令的输入输出方向也被确定了,直到再次遇到 exec 命令才会改变重定向的方向;换句话说,一次重定向,永久有效
exec
可以让重定向对当前 Shell 进程中的所有命令有效:exec 文件描述符操作
echo "重定向未发生"
重定向未发生
exec >log.txt
echo "c.biancheng.net"
echo "C语言中文网"
exec >&2
echo "重定向已恢复"
重定向已恢复
cat log.txt
c.biancheng.net
C语言中文网
exec >log.txt
将当前 Shell 进程的所有标准输出重定向到 log.txt
文件,它等价于exec 1>log.txt
。echo
命令都没有在显示器上输出,而是输出到了 log.txt
文件。exec >&2
用来恢复重定向,让标准输出重新回到显示器,它等价于exec 1>&2
。2
是标准错误输出的文件描述符,它也是输出到显示器,并且没有遭到破坏,我们用 2
来覆盖 1
,就能修复 1
,让 1
重新指向显示器。echo
命令将结果输出到显示器上,证明exec >&2
奏效了。cat
命令来查看 log.txt
文件的内容,发现就是中间两个 echo
命令的输出。重定向的恢复
以输出重定向为例,手动恢复的方法有两种:
/dev/tty
文件代表的就是显示器,将标准输出重定向到 /dev/tty
即可,也就是 exec >/dev/tty
。1
的文件描述符,例如 exec >&2
。注意,如果文件描述符 2
也被重定向了,那么这种方式就无效了Shell代码块重定向
代码块,就是由多条语句组成的一个整体;for、while、until 循环,或者 if…else、case…in 选择结构,或者由{ }包围的命令都可以称为代码块
将重定向命令放在代码块的结尾处,就可以对代码块中的所有命令实施重定向。
使用 while 循环不断读取 nums.txt 中的数字,计算它们的总和。
#!/bin/bash
sum=0
while read n; do
((sum += n))
done log.txt #同时使用输入输出重定向
echo "sum=$sum"
对{}包围的代码使用重定向。
#!/bin/bash
{
echo "C语言中文网";
echo "http://c.biancheng.net";
echo "7"
} >log.txt #输出重定向
{
read name;
read url;
read age
}
Shell Here Document(内嵌文档/嵌入文档
Here Document 的基本用法为:
command <
END
command
是 Shell 命令,<
END
是结束标志,document
是输入的文档(也就是一行一行的字符串)。
在脚本文件中使用 Here Document,并将 document 中的内容转换为大写。
#!/bin/bash
#在脚本文件中使用立即文档
tr a-z A-Z <
注意,终止符END
必须独占一行,并且要定顶格写。
分界符(终止符)<< 对应的END
可以是任意的字符串,由用户自己定义,比如 END、MARKER 等。分界符可以出现在正常的数据流中,只要它不是顶格写的独立的一行,就不会被作为结束标志
cat < Shell教程
> http://c.biancheng.net/shell/
> 已经进行了三次改版
> END
Shell教程
http://c.biancheng.net/shell/
已经进行了三次改版
忽略命令替换
默认情况下,正文中出现的变量和命令也会被求值或运行,Shell 会先将它们替换以后再交给 command
name=C语言中文网
url=http://c.biancheng.net
age=7
cat < ${name}已经${age}岁了,它的网址是 ${url}
> END
C语言中文网已经7岁了,它的网址是 http://c.biancheng.net
可以将分界符用单引号或者双引号包围起来使 Shell 替换失效:
name=C语言中文网
url=http://c.biancheng.net
age=7
cat <<'END' #使用单引号包围
> ${name}已经${age}岁了,它的网址是 ${url}
> END
${name}已经${age}岁了,它的网址是 ${url}
默认情况下,行首的制表符也被当做正文的一部分。
#!/bin/bash
cat <
这里的制表符仅仅是为了格式对齐,并不希望它作为正文的一部分,为了达到这个目的,可以在<<和END之间增加-
#!/bin/bash
#增加了减号-
cat <<-END
Shell教程
http://c.biancheng.net/shell/
已经进行了三次改版
END
这次的运行结果为:
Shell教程
http://c.biancheng.net/shell/
已经进行了三次改版
Shell Here String(内嵌字符串)
Here String
是 Here Document 的一个变种,它的用法如下:
command <<< string
单双引号区别:
var=two
tr a-z A-Z <<<"one $var there"
ONE TWO THERE
tr a-z A-Z <<<'one $var there'
ONE $VAR THERE
tr a-z A-Z <<
有了引号的包围,Here String 还可以接收多行字符串作为命令的输入:
tr a-z A-Z <<<"one two there
> four five six
> seven eight"
ONE TWO THERE
FOUR FIVE SIX
SEVEN EIGHT
管道(pipe):将两个或者多个命令(程序或者进程)连接到一起,把一个命令的输出作为下一个命令的输入
Linux 管道使用竖线|
连接多个命令,这被称为管道符。Linux 管道的具体语法格式如下:
command1 | command2
command1 | command2 [ | commandN... ]
当在两个命令之间设置管道时,管道符|
左边命令的输出就变成了右边命令的输入。只要第一个命令向标准输出写入,而第二个命令是从标准输入读取,那么这两个命令就可以形成一个管道。大部分的 Linux 命令都可以用来形成管道。
command1
必须有正确输出,而 command2
必须可以处理 command2
的输出结果;而且 command2
只能处理 command1
的正确输出结果,不能处理 command1
的错误信息。
mysqldump -u root -p '123456' wiki | gzip -9 | ssh username@remote_ip "cat > /backup/wikidb.gz"
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PDdRy6Is-1597903870364)(./res/shell-pipe.png)]
这些使用了管道的命令有如下特点:
管道与输入重定向
输入重定向操作符<可以在管道中使用,以用来从文件中获取输入:
command1 < input.txt | command2
command1 < input.txt | command2 -option | command3
tr a-z A-Z
管道与输出重定向
也可以使用重定向操作符>
或>>
将管道中的最后一个命令的标准输出进行重定向:
command1 | command2 | ... | commandN > output.txt
command1 < input.txt | command2 | ... | commandN > output.txt
tr a-z A-Z os.txt.new