Shell编程基础
前言
Shell 既是一种脚本编程语言,也是一个连接内核和用户的软件。Shell 将内核、程序和用户连接了起来。
任何代码最终都要被“翻译”成二进制的形式才能在计算机中执行。
有的编程语言,如 C/C++、Pascal、Go语言、汇编等,必须在程序运行之前将所有代码都翻译成二进制形式,也就是生成可执行文件,用户拿到的是最终生成的可执行文件,看不到源码。
这个过程叫做编译(Compile),这样的编程语言叫做编译型语言,完成编译过程的软件叫做编译器(Compiler)。
而有的编程语言,如 Shell、JavaScript、Python、PHP等,需要一边执行一边翻译,不会生成任何可执行文件,用户必须拿到源码才能运行程序。程序运行后会即时翻译,翻译完一部分执行一部分,不用等到所有代码都翻译完。
这个过程叫做解释,这样的编程语言叫做解释型语言或者脚本语言(Script),完成解释过程的软件叫做解释器。
编译型语言的优点是执行速度快、对硬件要求低、保密性好,适合开发操作系统、大型应用程序、数据库等。
脚本语言的优点是使用灵活、部署容易、跨平台性好,非常适合Web开发以及小工具的制作。
Shell 就是一种脚本语言,我们编写完源码后不用编译,直接运行源码即可。
如今的 IT 服务器领域是 Linux、UNIX、Windows 三分天下。Linux 在服务器上的应用非常广泛,可以用来搭建Web服务器、数据库服务器、负载均衡服务器(CDN)、邮件服务器、DNS服务器、反向代理服务器、VPN服务器、路由器等。
常见的 Shell 有 sh、bash、csh、tcsh、ash 等,常用bash。
基础知识
-
查看shell:
cat /etc/shells
-
输出shell环境变量:
echo $SHELL
-
Shell 通过PS1和PS2两个环境变量来控制提示符格式:
PS1 控制最外层命令行的提示符格式。
PS2 控制第二层命令行的提示符格式。
echo $PS1
echo $PS2
-
多行输出:
echo "
>one
>two
>three
>"
- 外部赋值read
#!/bin/bash
# Author : mozhiyan
# Copyright (c) http://see.xidian.edu.cn/cpp/linux/
# Script follows here:
echo "What is your name?"
read PERSON #read 命令从 stdin 获取输入并赋值给 PERSON 变量
echo "Hello, $PERSON"
- 变量定义
脚本语言在定义变量时通常不需要指明类型,直接赋值就可以。Bash shell 在默认情况下不会区分变量类型,即使你将整数和小数赋值给变量,它们也会被视为字符串。
注意,赋值时变量不加$,且赋值号的周围不能有空格
variable=value #value不含空白符
variable='value' #不能解析变量和命令
variable="value" #可解析变量和命令
url="http://c.biancheng.net"
website1='test1:${url}'
website2="test2:${url}"
echo $website1 #test1:${url}
echo $website2 #test2:http://c.biancheng.net
#定义变量时加双引号是最常见的使用场景。
- 使用变量
使用一个定义过的变量,只要在变量名前面加美元符号$即可。
author="严长生"
echo $author
echo ${author} #为了帮助解释器识别变量的边界
skill="Java"
echo "I am good at ${skill}Script"
#推荐给所有变量加上花括号{ },这是个良好的编程习惯。
- 命令结果赋值给变量
variable=`cd ~;ls;pwd`
variable=$(cd ~;ls;pwd)
log=$(cat log.txt)
- 修改变量
url="test1"
url="test2"
readonly url #设为只读变量
url="test3" #error
- 删除变量
unset variable
unset url #不能删除只读变量
- 特殊变量
$0 当前脚本的文件名
$n 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。
$# 传递给脚本或函数的参数个数。
$* 传递给脚本或函数的所有参数。
$@ 传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同,下面将会讲到。
$? 上个命令的退出状态(一般成功返回0,失败返回1),或函数的返回值。
$$ 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。
#$* 和 $@ 的区别
不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。
被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。
其他特殊变量和字符:
https://cloud.tencent.com/developer/article/1176708
- shell替换
# 转义字符
\\ \a \b \f \n \r \t \v......
#!/bin/bash
a=10
echo -e "Value of a is $a \n"
# -e 表示对转义字符进行替换。如果不使用 -e 选项,将会原样输出:Value of a is 10\n。
可以使用 echo 命令的 -E 选项禁止转义,默认也是不转义的;使用 -n 选项可以禁止插入换行符。
# 命令替换
#!/bin/bash
DATE=`date`
echo "Date is $DATE"
USERS=`who | wc -l`
echo "Logged in user are $USERS"
UP=`date ; uptime`
echo "Uptime is $UP"
# 变量替换
根据变量的状态(是否为空、是否定义等)来改变它的值。
${var:-word} 如果变量 var 为空或已被删除(unset),那么返回 word,但不改变 var 的值。
${var:=word} 如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word。
${var:+word} 如果变量 var 被定义,那么返回 word,但不改变 var 的值。
${var:?message} 如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。
- shell运算符
原生bash不支持简单的数学运算,但是可以通过awk 和 expr等其他命令来实现,expr 最常用。
#!/bin/bash
val=`expr 2 + 2`
echo "Total value : $val"
#1.算术运算符
+ - * / % == !=
#!/bin/sh
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val"
val=`expr $a \* $b` #需加转义符
echo "a * b : $val"
val=`expr $b / $a`
echo "b / a : $val"
val=`expr $b % $a`
echo "b % a : $val"
if [ $a == $b ]
then
echo "a is equal to b"
fi
if [ $a != $b ]
then
echo "a is not equal to b"
fi
#2.关系运算符
-eq -ne -gt -lt -ge -le
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
#!/bin/sh
a=10
b=20
if [ $a -eq $b ]
then
echo "$a -eq $b : a is equal to b"
else
echo "$a -eq $b: a is not equal to b"
fi
#3.布尔运算符
!(非) -o(或) -a(与)
#!/bin/sh
a=10
b=20
if [ $a -lt 100 -o $b -gt 100 ]
then
echo "$a -lt 100 -o $b -gt 100 : returns true"
else
echo "$a -lt 100 -o $b -gt 100 : returns false"
fi
#4.字符串运算符
=
!=
-z(长度是否为0,0为TRUE)
-n(长度是否为0,0为FALSE)
str(字符串是否为空,不为空TRUE)
#!/bin/sh
a="abc"
b="efg"
if [ $a != $b ]
then
echo "$a != $b : a is not equal to b"
else
echo "$a != $b: a is equal to b"
fi
if [ -z $a ]
then
echo "-z $a : string length is zero"
else
echo "-z $a : string length is not zero"
fi
if [ $a ]
then
echo "$a : string is not empty"
else
echo "$a : string is empty"
fi
#5.文件测试运算符
用于检测 Unix 文件的各种属性:
-e 文件/目录是否存在
-d 是否为目录
-f 是否为普通文件(不是目录和设备文件)
-s 文件是否为空
-r 是否可读
-w 可写
-x 可执行
-b 块特殊文件
-c 字符型特殊文件
-g -k -p -u ......
#!/bin/sh
file="/var/www/tutorialspoint/unix/test.sh"
if [ -e $file ]
then
echo "File exists"
else
echo "File does not exist"
fi
-
shell注释
批量注释:
0.每行加#(麻烦,但是最保险);
1.将{}括起来定义为一个函数,不进行调用;
2.<<EOF ...EOF;
3.'...' (最简单,但会有风险,如不会注释脚本中本身带有单引号的语句部分,一般临时用用)。 -
shell字符串
#单引号
str='this is a string' #不能内插变量和转义
#双引号
your_name='qinjx'
str="Hello, I know your are \"$your_name\"! \n"
#字符串拼接
your_name="qinjx"
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1
#获取字符串长度
string="abcd"
echo ${#string} # 输出4
#提取子字符串
string="alibaba is a great company"
echo ${string:1:4} #输出liba
#查找子字符串
string="alibaba is a great company"
echo `expr index "$string" is` #输出3。查找string中is的索引,实际上以第一个字符i为主,因此为3
- shell数组
bash仅支持一维数组。
#定义数组
array=(0 1 2 3 4) #下标从0开始,元素注意是空格
或
array=(
0
1
2
3
4
)
或
array[0]=0 #单独定义各分量
array[1]=1
array[2]=2
#读取数组
${array[index]}
value=${array[2]}
读取数组中所有元素:
${array[*]}
或
${array[@]}
#获取数组长度
与获取字符串长度的方法相同:
length=${#array[@]}
或
length=${#array[*]}
length1=${#array[0]} #获取数组元素长度
命令与结构控制
- echo命令
echo后隐式自带换行符\n
echo "\"It is a test\"" #"It is a test"
name="OK"
echo "$name It is a test" #OK It is a test
#如果变量与其它字符相连的话,需要使用大括号{ }
mouth=8
echo "${mouth}-1-2009" #8-1-2009
echo "OK!\n" #原样输出OK!\n
echo "It is a test"
echo -e "OK!\n"
echo "It is a test" #加入了一个空行
echo -e "OK!\nthis a test"
echo "It is a test" > myfile
echo `date`
- sprintf命令
由 POSIX 标准所定义,移植性要比 echo 好。
printf "Hello, Shell\n" #Hello,Shell 不同于echo,必须显式添加换行符\n
#格式化输出:
printf "%d %s\n" 1 "abc" # 1 abc 加双引号
printf '%d %s\n' 1 "abc" #加单引号
printf %s abcdef #不加引号
printf "%s and %d \n" # and 0 若无参数,%s 用NULL代替,%d 用 0 代替
printf "%-10s %-8s %-4.2f\n" GuoFu female 47.9876
#GuoFu female 47.99
#%s %c %d %f都是格式替代符,%-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
%-4.2f 指格式化为小数,其中.2指保留2位小数。
- if ... else 语句
最后必须以 fi 来结尾闭合 if,fi 就是 if 倒过来拼写。
注意:表达式和方括号[ ]之间必须有空格,否则会有语法错误。
#!/bin/sh
a=10
b=20
if [ $a == $b ]
then
echo "a is equal to b"
fi
#if ... elif ... fi 语句对多个条件进行判断:
#!/bin/sh
a=10
b=20
if [ $a == $b ]
then
echo "a is equal to b"
elif [ $a -gt $b ]
then
echo "a is greater than b"
elif [ $a -lt $b ]
then
echo "a is less than b"
else
echo "None of the condition met"
fi
#if ... else 语句也经常与 test 命令结合使用,test 命令用于检查某个条件是否成立,与方括号[ ]类似。
#简写成一行,以命令的方式来运行
if test $[2*3] -eq $[1+5]; then echo 'The two numbers are equal!'; fi;
- test命令
用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。
#数值测试
-eq -ne -gt -ge -lt -le
num1=100
num2=100
if test $[num1] -eq $[num2]
then
echo 'The two numbers are equal!'
else
echo 'The two numbers are not equal!'
fi
#字符串测试
= != -z -n
num1=jesse
num2=jessi
if test num1=num2
then
echo 'The two strings are equal!'
else
echo 'The two strings are not equal!'
fi
#文件测试
-e -r -w -x -s -d -f -c -b
cd /bin
if test -e ./bash
then
echo 'The file already exists!'
else
echo 'The file does not exists!'
fi
#与逻辑操作符连用
! -o(或) -a
cd /bin
if test -e ./notFile -o ./bash
then
echo 'One file exists at least!'
else
echo 'Both dose not exists!'
fi
- case...esac语句
多分支选择结构,与其他语言中的 switch...case 语句类似。格式如下:
case 值 in
模式1)
command1
command2
command3
;;
模式2)
command1
command2
command3
;;
*)
command1
command2
command3
;;
esac
取值后面必须为关键字in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。;; 与其他语言中的 break 类似,意思是跳到整个 case 语句的最后。
取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。
echo 'Input a number between 1 to 4'
echo 'Your number is:\c'
read aNum
case $aNum in
1) echo 'You select 1'
;;
2) echo 'You select 2'
;;
3) echo 'You select 3'
;;
4) echo 'You select 4'
;;
*) echo 'You do not select a number between 1 to 4'
;;
esac
实际应用中,常用于传参:运行sh,不加参数,或加-f/-d参数的结果
#!/bin/bash
option="${1}"
case ${option} in
-f) FILE="${2}"
echo "File name is $FILE"
;;
-d) DIR="${2}"
echo "Dir name is $DIR"
;;
*)
echo "`basename ${0}`:usage: [-f file] | [-d directory]"
exit 1 # Command to come out of the program with status 1
;;
esac
如shell脚本在流程中传参:
- for循环
一般格式:
for 变量 in 列表
do
command1
command2
...
commandN
done
列表是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。每循环一次,就将列表中的下一个值赋给变量。
in 列表是可选的,如果不用它,for 循环使用命令行的位置参数。
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
for str in 'This is a string'
do
echo $str
done
#!/bin/bash
for FILE in $HOME/.bash* #显示主目录下以 .bash开头的文件
do
echo $FILE
done
- while循环
while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件,命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假。其格式为:
while command
do
Statement(s) to be executed if command is true
done
示例:
COUNTER=0
while [ $COUNTER -lt 5 ]
do
COUNTER='expr $COUNTER+1'
echo $COUNTER
done
while循环可用于读取键盘信息。在生信中,经常用于批量处理同路径下文件:
echo 'type <CTRL-D> to terminate'
echo -n 'enter your most liked film: '
while read FILM
do
echo "Yeah! great film the $FILM"
done
while read ID
do
command $ID
done
- until循环
与 while 循环在处理方式上刚好相反。一般while循环优于until循环,但在某些时候,也只是极少数情况下,until 循环更加有用。
#!/bin/bash
a=0
until [ ! $a -lt 10 ]
do
echo $a
a=`expr $a + 1`
done
- 跳出循环
Shell也使用 break 和 continue 来强制跳出循环。
break立即跳出所有循环
#!/bin/bash
while :
do
echo -n "Input a number between 1 to 5: "
read aNum
case $aNum in
1|2|3|4|5) echo "Your number is $aNum!"
;;
*) echo "You do not select a number between 1 to 5, game is over!"
break
;;
esac
done
break n
可以指定跳出第几层循环
#!/bin/bash
for var1 in 1 2 3
do
for var2 in 0 5
do
if [ $var1 -eq 2 -a $var2 -eq 0 ]
then
break 2 #当var1等于2,且var2等于0时,直接跳出外层循环
else
echo "$var1 $var2"
fi
done
done
continue跳出当前循环
#!/bin/bash
while :
do
echo -n "Input a number between 1 to 5: "
read aNum
case $aNum in
1|2|3|4|5) echo "Your number is $aNum!"
;;
*) echo "You do not select a number between 1 to 5!"
continue
echo "Game is over!" #永远不会执行
;;
esac
done
#!/bin/bash
NUMS="1 2 3 4 5 6 7"
for NUM in $NUMS
do
Q=`expr $NUM % 2`
if [ $Q -eq 0 ]
then
echo "Number is an even number!!" #偶数
continue
fi
echo "Found odd number"
done
continue 后面也可以跟一个数字,表示跳出第几层循环。一般不会这么用。
- shell函数
Shell 函数必须先定义后使用。定义格式:
function_name () {
list of commands
[ return value ]
}
Shell 函数返回值只能是整数,一般用来表示函数执行成功与否,0表示成功,其他值表示失败。默认最后一条命令运行结果作为返回值。
如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值。
调用函数只需要给出函数名,不需要加括号。
#!/bin/bash
funWithReturn(){
echo "The function is to get the sum of two numbers..."
echo -n "Input first number: "
read aNum
echo -n "Input another number: "
read anotherNum
echo "The two numbers are $aNum and $anotherNum !"
return $(($aNum+$anotherNum)) #需要两个括号
}
funWithReturn
# Capture value returnd by last command
ret=$?
echo "The sum of two numbers is $ret !"
函数嵌套
#!/bin/bash
# Calling one function from another
number_one () {
echo "Url_1 is http://see.xidian.edu.cn/cpp/shell/"
number_two
}
number_two () {
echo "Url_2 is http://see.xidian.edu.cn/cpp/u/xitong/"
}
number_one
删除函数:
unset .f function_name
- shell函数参数
在函数体内部,通过$n的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...
#!/bin/bash
funWithParam(){
echo "The value of the first parameter is $1 !"
echo "The value of the second parameter is $2 !"
echo "The value of the tenth parameter is $10 !"
#注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。
echo "The value of the tenth parameter is ${10} !"
echo "The value of the eleventh parameter is ${11} !"
echo "The amount of the parameters is $# !" # 参数个数
echo "The string of the parameters is $* !" # 传递给函数的所有参数
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
- shell输入输出重定向
默认从标准输入设备(stdin)获取输入,将结果输出到标准输出设备(stdout)显示。一般情况下,标准输入设备就是键盘,标准输出设备就是终端,即显示器。
输出重定向:
command >file
who > users
echo line 1 >users #输出重定向会覆盖文件内容
echo line 2 >> users #不希望文件内容被覆盖,可以使用>>追加到文件末尾
输入重定向:
从文件读取内容。
wc -l users #2 users。会输出文件名
wc -l < users #2。不会输出文件名,因为它仅仅从标准输入读取内容
重定向符号:
标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。
标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。
标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。
command 2 > file #stderr重定向到file
command 2 >> file
command > file 2>&1 #将stdout和stderr合并后重定向到file
command >> file 2>&1
command < file1 >file2 #对stdin和stdout都重定向,将stdin重定向到file1,将stdout重定向到file2。
Here document(嵌入文档)
Shell 中的一种特殊的重定向方式。形式如下:
command << delimiter
document
delimiter
它的作用是将两个delimiter(后一个顶格写)之间的内容(document) 作为输入传递给 command。
wc -l << EOF
This is a simple lookup program
for good (and bad) restaurants
in Cape Town.
EOF
/dev/null文件
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null。 /dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出“的效果。
command > /dev/null
command > /dev/null 2>&1 #屏蔽 stdout 和 stderr
- shell调用
像其他语言一样,Shell 也可以包含外部脚本,将外部脚本的内容合并到当前脚本。
. filename (点号后有空格),更常用。
或 source filename
示例:
#被调用脚本 subscript.sh内容:
url="http://see.xidian.edu.cn/cpp/view/2738.html"
#主文件 main.sh内容:
#!/bin/bash
. ./subscript.sh
echo $url
#执行:
chomd +x main.sh
./main.sh
注意:被包含脚本不需要有执行权限。
本文来自博客园,作者:生物信息与育种,转载请注明原文链接:https://www.cnblogs.com/miyuanbiotech/p/11204314.html。若要及时了解动态信息,请关注同名微信公众号:生物信息与育种。