Shell脚本与Windows/Dos下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件, Shell就是一个命令行解释器,Shell本身是一个用C语言编写的程序, Shell既是一种命令语言,又是一种程序设计语言(就是你所说的shell脚本)。shell脚本脚本文件通常以.sh作为后缀名,第一行以#!开头指定执行脚本的程序:
#!/usr/bin/bash
#是shell脚本中的行注释符。
这里我们来使用最常用的bash
1.新建文件hello_shell
$ vi hello_shell
2. 输入Shello命令
#! /bin/bash
echo "Hello Shell!"
3、保存
echo是一个输出命令,就是输出一句话
4. 赋权限
我们要让系统知道我们刚才新建的文件是可执行的,所以我们要赋给其可执行的权限
现在我们的文件是不可执行的,只有读写权限:
下面是赋权
$ chmod 711 hello_shell
5. 执行
./ 表示再当前目录查找命令,如果什么都不加的话,系统默认会在PATH里寻找,而的当前目录通常不在PATH里,所以找不到命令。
通常有三种执行脚本的方式:
1、sh start.sh: 在终端中创建一个sh子进程执行脚本, 执行者需要拥有脚本的读权限。该方式实际上是将脚本路径作为参数传递给了sh命令。
2、source start.sh: 在终端中执行脚本,相当于将脚本中的指令逐条复制到终端执行。脚本中局部变量将保留在终端环境变量中, 脚本的pid和工作目录等环境也与终端一致。
3、./start.sh: 根据HashBang指定的程序,在子进程中执行脚本。
1. 变量的声明和定义
Shell里的变量类型:字符串、数值。定义的方式其实是一样的,字符串用单引号或双引号标识。
Shell里变量命名规范:首个字符必须为字母(a-z,A-Z)任何变量都只能由字母(包括大小写)、数字和下划线组成变量中不能有空格,不能使用bash里的关键字(可用help命令查看保留关键字)
变量在使用前无需声明,在为变量赋值时=左右不能添加空格。
y_name="yuguiyang"
y_age=24
2. 变量的使用
使用一个定义过的变量,只要在变量名前面加美元符号$即可
y_name="yuguiyang"
y_age=24
echo "name:$y_name"
echo "age:$y_age"
我们也可以使用 {}将变量括起来,加花括号是为了帮助解释器识别变量的边界
A=a
AB=ab
echo ${A}B
可以把命令的输出作为返回值, 如:
PWD=$(pwd)
在单引号标识的字符串中$不被作为变量标识符, 而双引号则会将$替换为变量内容。
A="abc"
echo ‘$A‘ # $A
echo "$A" # abc
字符串拼接不需要任何运算符,只需要将它们写在一起即可:
A="abc"
B="123"
echo "$A+$B" # abc+123
echo "$A$B" # abc123
echo "$Adef" # abcdef
3、整型变量
shell仅支持整型计算, declare命令可以声明整型变量,let指令用于算术运算:
declare -i a=1
let a=a+1
echo $a # 2
let a+=1
echo $a # 3
let指令支持算术运算符包括:
+:加法
-: 减法
*: 乘法
/: 除法
**: 乘方
%: 取余
let指令也支持算术运算符对应的算术赋值运算符,如+=。
4. 只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变
#!/bin/bash
y_id=1990
readonly y_id
echo "y_id:${y_id}"
y_id=2014
echo "y_id:${y_id}"
这里我们尝试修改只读变量y_id,运行时,会报错,提示该变量为只读变量:
5. 删除变量
使用 unset 命令可以删除变量,变量被删除后不能再次使用;
unset 命令不能删除只读变量。
#!/bin/bash
y_id=1990
y_name="yuguiyang"
echo "y_id:${y_id}"
echo "y_name:${y_name}"
readonly y_id
unset y_id
unset y_name
echo "y_id:${y_id}"
echo "y_name:${y_name}"
由于y_id 是只读变量,所以不会被删除,y_name被删除后,输出为空
结果:
6. 变量的作用范围
运行shell时,会同时存在三种变量:
1) 局部变量
局部变量在脚本或命令中定义,作用域仅限执行脚本的进程,即仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
2) 环境变量(全局变量)
所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。子进程可以继承父进程的全局变量。环境变量配置保存于/etc/profile文件中。可以通过export来查看。也可以通过export来设置环境变量:export xxx=”yyy”
set |grep xxx可以查看某个具体的变量的值
3) shell变量
shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
7. 特殊变量
shell中预定义了一些特殊变量,通过这些变量可以获得环境信息:
●$$: 执行当前脚本的进程ID(pid)
●$?: 上一条命令的返回值,如果上一个命令成功执行则$?的值为0,否则为其他非零值
●$!: 上一条后台指令的执行进程的ID
上述变量在交互式终端中同样有效。
还有一些变量可以获得执行脚本时传入的参数:
●$0: 当前脚本的文件名
●$1~$n: 传给脚本的第n个参数
●$#: 传入脚本的参数的个数
●$@: 参数列表
●$*: 单个字符串形式的参数列表
bash中可以使用圆括号定义数组,元素之间用空格分割,数组下标从0开始:
1. 定义
Shell脚本中支持一维数组,下标从0开始
我们可以这样
y_books[0]="today"
y_books[1]="onepiece"
可以这样:
y_books=("one" "two" "three")
还可以这样:
y_books=(
"one"
"two"
"three")
2. 读取数组内容
我们使用下标来获取数组的信息
echo "book:${y_books[0]}"
echo "book:${y_books[1]}"
3. 获取所有的元素
使用*或者@
示例:
echo "books:${y_books[*]}"
echo "books:${y_books[@]}"
4. 获取数组长度
echo "数组长度"
echo "length:${#y_books[*]}"
echo "length:${#y_books[@]}"
之前我们都是直接将内容输出,那怎样可以交互呢?我们使用read命令,read可以直接使用而不需要在前面声明这个变量。
#!/bin/bash
echo "请输入你的名字:"
read y_name
echo "你好,${y_name}"
#!/bin/bash
echo ""
echo "姓名:"
read y_name
echo "年龄:"
read y_age
echo ""
echo "${y_name},今年${y_age}岁。"
Linux中有3类运算符:算术运算符、逻辑运算符、比较运算符
1. 算术运算符
在学习运算符之前我们先看个东西,之前没有注意到:
#!/bin/bash
y_price=10.9
y_total=${y_price}+3
echo "y_total:${y_total}"
y_total应该输出13.9?不是的,输出值为:10.9+3。因为Shell中默认把变量看作是字符串,所以才会显示成这样。要解决这个问题,我们需要使用let命令
let val="3 + 6"
echo "val:${val}"
a=5
b=9
let val="a + b"
echo "val:${val}"
let val="${a} + ${b}"
echo "val:${val}"
注意:等号(=)两边没有空格,而加号(+)两边有空格,且所有的运算符两边都要有空格。
从上面的例子可以发现:let表达式后,调用变量时可以不使用$符号let也可以使用(())代替
((val="10 + 30"))
echo "val:${val}"
注意:let只可以计算整数,不可以算浮点数
2. 逻辑运算符
3. 比较运算符
#!/bin/bash
a=3
b=4
test ${a} -eq ${b}
echo "$?"
我们这里使用test来判断a和b是否相等,$?可以返回真、假。对于运算符,我们先说到这,我们会在后面的练习中使用
这里的if语句和其他开发语言中的差不多, Shell脚本中的if语句有3种
1. if ... then ... fi
Condition如果为真,则执行then后面的语句,为假则结束。这里需要注意的是:Condition和方括号之间需要有空格,否则就会报错
#!/bin/bash
echo "3+3=?"
read y_result
if [ ${y_result} -eq 6 ]
then
echo "Ha,good."
fi
结果:
2. if ... then ... else ... fi
这个多了个else,可以对不符合表达式时做些处理
#!/bin/bash
echo "3+3=?"
read y_result
if [ ${y_result} -eq 6 ]
then
echo "Ha,good."
else
echo "Oh,wrong."
Fi
3. if ... elif ... fi
有时我们想要在else的时候,再做些判断,可以使用elif
#!/bin/bash
echo "3+3=?"
read y_result
if [ ${y_result} -eq 6 ]
then
echo "Ha,good."
elif [ ${y_result} == -1 ]
then
echo "Hehe,you find me."
Fi
<, >等运算符在[]中只能用于字符串的比较, 而在[[]]中<, >可以用于整型和字符串的大小比较, 也可以使用&&和||来书写逻辑表达式。
if的条件判断不一定使用[]或[[]]表达式,它可以是任何一个命令。命令的返回值为0则if判断为真, 非0判断为假。
[]和[[]]转义表达式也可以像普通指令一样执行,判断为真则返回0,假则返回非0值。
[ 2 -gt 1 -a 3 -lt 4 ] && echo ‘ok‘
判断字符串相等
if [${NAME}
=
‘tmp‘ ]
;
then
echo
"name is tmp"
fi
判断文件是否存在
if [-e
tmp
];
then
echo
"tmp exists"
fi
判断tmp
是否存在,tmp
可以是目录或文件。
判断是否为普通文件
if [-f
tmp
];
then
echo
"file tmp exists"
fi
判断tmp
是否为文件,tmp
不能是目录。
判断是否为目录
if [-d
tmp
];
then
echo
"directory tmp exists"
fi
判断是否具有执行权限
if [-x
tmp
];
then
echo
"tmp is executable"
fi
不判断文件是否可执行,只判断是否拥有x
权限。 因此,tmp为有x权限的目录时也会判断为真。类似的还有,-w
判断是否拥有写入权限, -r
判断是否拥有读取权限。
判断是否为空文件
if [-s
tmp
];
then
echo
"file is not empty"
fi
条件控制语句还有一个case,对于需要多个elif的可以使用case尝试下
#!/bin/bash
echo "choose a number from 1 to 4."
read y_num
case ${y_num} in
1)
echo "you select 1."
;;
2)
echo "you select 2."
;;
3)
echo "you select 3."
;;
4)
echo "you select 4."
;;
*)
echo "please choose a number from 1 to 4."
;;
esac
*可以代表一种默认情况
;; 与其他语言中的 break 类似,意思是跳到整个 case 语句的最后。
前面我们介绍了条件控制语句,这里我们介绍下循环控制语句while
同样的,Condition左右都需要有空格,while循环和Java中的都差不多,这里也不赘述什么了
#!/bin/bash
clear
echo "while demo"
y_result="ygy"
while [ ${y_result} != "lufei" ]
do
echo "Who are you ?"
read y_result
done
echo "Haha"
这里我们判断y_result变量的内容是否为“lufei",否则一直循环
#!/bin/bash
clear
echo "乘法表"
let y=1
while [ ${y} -le 9 ]
do
let x=1
while [ ${x} -le ${y} ]
do
let rs="${x} * ${y}"
printf "${x} * ${y} = ${rs} "
let x="${x} + 1"
done
let y="${y} + 1"
echo ""
done
代码还好,就是个逻辑,相信大家会有更好的方法来实现
for会把wordlist中的值按顺序赋给变量,并执行循环体中的内容
wordlist是一个列表,我们看个例子就知道了
#!/bin/bash
echo ""
for loop in 1 2 3 4 5
do
echo "hello ${loop} ."
done
一些命令的输出也可以作为序列:
for i in $(ls); do
echo $i
done
Shell脚本里面也可以定义函数,函数就像是脚本中的子脚本
1.函数的定义和调用
我们使用 function 来定义一个函数,需要用括号括起来
function show_menu {
echo ""
echo "1.显示所有联系人"
echo "2.退出"
echo ""
echo "请选择:"
}
调用的话,直接使用函数名进行调用
下面的函数我们显示个界面,根据用户输入进行判断
#!/bin/bash
#显示菜单
function show_menu {
echo ""
echo "1.显示所有联系人"
echo "2.退出"
echo ""
echo "请选择:"
}
#循环标识,为1时进行循环
let flag=1
while [ ${flag} == 1 ]
do
#显示菜单信息
show_menu
#读取输入
read rs
case ${rs} in
1)
echo "暂无联系人。"
;;
2)
echo "Bye."
let flag=0
;;
*)
echo "请选择1或者2"
;;
esac
done
2. 函数的参数
函数同样可以传递参数的,我们可以使用 ${1},${2}..来使用
#!/bin/bash
function logon {
if [ ${1} == "lufei" ] && [ ${2} == "haha" ]
then
echo "登录成功!"
else
echo "登录失败!"
fi
}
let flag=1
while [ ${flag} -eq 1 ]
do
echo "请输入用户名:"
read u_name
echo "请输入密码:"
read u_password
logon ${u_name} ${u_password}
done
这里是一个简单的登录验证示例,我们将获取的用户名和密码传递给函数logon去验证
4. 参数个数验证
在这里调用函数的时候很可能参数不够,导致程序出错;我们可以使用$#来获取参数的个数进行判断
#!/bin/bash
function logon {
#判断参数个数,2个参数就进行信息验证
if [ $# -eq 2 ]
then
if [ ${1} == "lufei" ] && [ ${2} == "haha" ]
then
echo "登录成功!"
else
echo "登录失败!"
fi
else
#提示错误信息
echo "参数个数不符"
fi
}
let flag=1
while [ ${flag} -eq 1 ]
do
echo "请输入用户名:"
read u_name
echo "请输入密码:"
read u_password
#调用时,只传了一个参数
logon ${u_name}
done
这个程序,暂时还不能退出,可以使用CTRL+C强制退出
5. 函数返回值
函数可以有返回值,return,但是这里的话,返回值必须是0~256之间的一个整数
#!/bin/bash
function test_return {
echo "please input a number:"
read num
return ${num}
}
test_return
echo "haha,you input: $?"
在实际开发中,我们的程序可能比较大,需要按模块开发,有不同的子程序
每个子程序都是独立的一个 文件,我们可以在一个主程序中调用他们
#!/bin/bash
clear
echo ""
echo "1.Add"
echo "2.Delete"
echo "3.Display"
echo "4.Quit"
read selection
case ${selection} in
"1")
./child_add
;;
"2")
./child_delete
;;
"3")
./child_display
;;
"4")
./child_quit
;;
*)
echo "oh,no."
;;
esac
这是我们的一个主程序,我们根据输入,调用不同的程序
就子程序的话,先说到这,就是一个简单的例子
3. 显示文件中的内容
#!/bin/bash
echo "显示文件中的信息"
echo "f_users.bat"
echo "---------------"
cat f_users.bat
1. linux下常用的输入输出操作符
标准输入(stdin): < 或者 <<
标准输出(stdout): >或者 >>;
标准正确输出(stderr): 1>或者1>> >或者 >> ;
标准错误输出(stderr): 2>或者2>>;
2. 输出重定向
我们使用 > 或者 >>
我们使用 ls命令来显示2个文件,其中file02存在,而file03不存在,这样我们会输出一条错误信息,一条正确信息
1. 我们将正确信息输出到文件中
默认会将正确信息输出,所以这2种写法都可以
输出到文件同样使用echo命令:
1. 写入文件
#!/bin/bash
echo "write to file."
echo "iput your name:"
read y_name
echo "Hello,${y_name}" > f_users.bat
我们这里使用 > 将信息重定向到了f_users.bat这个文件中。如果原来已经有一个同名的文件,使用大于号(>)会覆盖这个文件
2. 追加信息
我们使用 >>可以向文件中追加信息
#!/bin/bash
echo "write to file."
echo "iput your name:"
read y_name
echo "Hello,${y_name}" >> f_users.bat
在我们之前的例子中,我们经常使用echo命令将一些信息输出,这回我们来详细了解下echo这个命令
echo -n :输出后不会自动换行
echo -e :会对一些字符做特殊处理
printf同样可以输出信,但printf没有像echo一样自动换行
printf可以对输出进行格式化
如果需要限定输出的宽度,格式为%flags width.precision format-specifier,width表示宽度,是整数,默认是右边对齐,如果需要左边对齐,在前面加“-”,
例如"%-20s"表示从左边开始对齐,宽度为20,如果字符串长度少于20,通过空格补齐,长度超过20,则会完全显示。
precision在浮点值中提供四舍五入。例如%5.6G,长度为5,精度为6。精度是可选的。长度和精度的值可以参数中指定,例如printf "%*.*G/n" 5 6 $myvalue。
长度指显示中占的字符长度,与字符长度的同义。如果长度比实际的少,例如实际字符长度更大或者所要求的精度更大,则显示按实际长度。
shell可以执行一行指令后立即返回, 返回后可以通过$?变量获得执行进程的ID:
$ sleep 10 &
[1] 79403
$ echo $!
79403
Linux中可以使用分号“;”、双and号“&&”和双竖线“||”来连接多个命令。单"&"符号也算命令连接符号,只不过它是将其前面的命令放入后台执行,所以可以变相地实现命令并行执行。
command1 ; command2
命令之间没有逻辑关系。分号连接的命令会按照顺序从前向后依次执行,但分号两端的命令之间没有任何逻辑关系,所有写出来的命令最终都会被执行,即使分号前面的命令出错也不影响后面的命令。
[root@xuexi ~]# ls das;echo "hdakl"
ls: cannot access das: No such file or directory
hdakl
command1 && command2
逻辑与。&&连接的命令会按照顺序从前向后执行,但只有当command1正确执行才执行command2,如果command1不正确执行,则不执行command2。在bash中,通过预定义变量“$?”来判断命令是否正确执行,如果"$?"的值为0则表示前一条命令正确执行,其他任意值都表示不正确执行。
[root@xuexi ~]# echo "hdakl" && ls ds
hdakl
ls: cannot access ds: No such file or directory
[root@xuexi ~]# ls das && echo "hdakl"
ls: cannot access das: No such file or directory
command1 || command2
逻辑或。||连接的命令会按照顺序从前向后执行,但只有当command1不正确执行才执行command2,command1正确执行则不会执行command2。||和&&都是短路符号,符号左右的命令之间具有逻辑关系。
[root@xuexi ~]# ls das || echo "hdakl"
ls: cannot access das: No such file or directory
hdakl
[root@xuexi ~]# echo "hdakl" || ls ds
hdakl
一般要联合使用&&和||的时候,基本上都会先逻辑与再逻辑或:command1 && command2 || command3。因为在实际中,command2和command3应该都是想要执行的命令。如果command1正确执行,$?就等于0,执行command2,再看情况执行command3,如果command1错误执行,$?就不等于0,所以不执行command2,根据$?为非0值,判断了 || 右边的命令应该被执行。
通俗点的理解方法是根据语义判断。“如果...就...否则...就...”的语句使用“cmd1 && cmd2 || cmd3”,“如果不...就...否则...就...”使用“!cmd1 && cmd2 || cmd3”。
例如,如果用户user1存在,就显示用户已经存在,否则,就添加此用户。
[root@xuexi tmp]# id user1 && echo "user1 exists" || useradd user1
如果用户user2不存在,则添加此用户,否则显示用户已存在。
[root@xuexi tmp]# !id user2 && useradd user2 || echo "user2 exists"
如果用户user3不存在,则添加此用户,并设定其密码为用户名本身,否则显示用户已存在。
[root@xuexi tmp]# !id user3 && useradd user3 && echo "user3" | passwd --stdin user3 || echo "user3 exists"
command1 &
command1 & command2
&表示将其前面的命令放入后台执行,放入后台后会立即返回到bash环境让用户可以继续和bash交互。如果&符号连接了两个命令,则其前面的命令被放入后台,立即执行后面的命令,所以可以简单地认为这两个命令是并行执行的,两端的命令之间也没有任何逻辑关系。
需要注意的一点是,在终端的bash环境下,子shell中的后台的进程不受终端控制,在终端被关闭时它会挂靠在init/systemd进程下,因此退出终端或脚本shell环境,无法中断这些后台进程。例如:
[root@xuexi ~]# (sleep 10 &) # 终端1上执行,立即关闭该终端
[root@xuexi ~]# ps aux | grep slee[p] # 终端2上捕捉sleep进程
root 5732 0.0 0.0 107892 624 ? S 00:28 0:00 sleep 10
注意ps结果中的"?",它表示非终端进程,即脱离了终端。
原文:https://www.cnblogs.com/tester-l/p/11398779.html