一、如何创建新shell脚本?
#!/bin/bash ###表示此脚本用/bin/bash运行,写什么就在什么环境下执行
2.怎么执行脚本?
1>sh
示例:#sh test.sh ###将hello.sh作为参数传给sh(bash)命令来执行的。这时不是hello.sh自己来执行,而是被人家调用执行,所以不要执行权限,不需要写第一行
2>给脚本加执行权限, 使文件可执行(使用chmod +x scripts)
示例:
#chmod +x test.sh
#/mnt/test.sh ###以绝对路径执行脚本,此时运行环境是脚本第一行写的
3. 将文件放置在用户的$PATH的目录中
可以直接用文件名执行,不管在哪个路径下
/usr/local/bin – 本地开发、系统上的其他人使用的脚本
/usr/local/sbin - 本地开发、由root使用的脚本
直接运行脚本和使用source命令运行脚本是不同的!
脚本调试模式:
#!/bin/bash -x
# bash -x scripts
三个符号:
\ :
1.放在指令前,有取消 aliases(别名) 的作用;
2.放在特殊符号前,则该特殊符号的作用消失;
3.放在指令的最末端,表示指令连接下一行(使得回车符无效,只起换行作用)
' ':强引用单引号中字符除单引号本身之外都解释为字面意义,单引号不具备引用变量的功能。单引号用于保持引号内所有字符的字面值,即使引号内的\和回车和``也不例外。
" ":弱引用,引用除美元符号($)、反引号(`)和反斜线(\)之外的所有字符。即在双引号中保持美元符号($)、反引号(`)和反斜线(\)符号的特殊含义,如"$变量名"表示以变量值替换变量名。利用双引号引用变量能够防止字符串分割,保留变量中的空格。
" "的就近原则:
[root@localhost mnt]# echo ""* * * *""
passwdfile test1.sh test2.sh username passwdfile test1.sh test2.sh username passwdfile test1.sh test2.sh username passwdfile test1.sh test2.sh username
前面两个引号成了一对,后面两个为一对
正确表示:
[root@localhost mnt]# echo "\"* * \""
"* * "
bash -x 脚本名:调试脚本
+ echo '?' 第一行先写出脚本某行
? 第二行写结果
二、变量
1.环境变量:
全局变量:存在与所有的shell 中,在你登陆系统的时候就已经有了相应的系统定义的环境变量了。Linux 的环境变量具有继承性,即子shell 会继承父shell 的环境变量。
本地变量:当前shell 中的变量,很显然本地变量中肯定包含环境变量。Linux 的本地变量的非环境变量不具备继承性。
env /printenv:用于显示系统中已经存在的环境变量
set:用于显示与设置当前本地 变量。单独一个set 就显示了当前环境的所有的变量,它肯定包括环境变量和一些非环境变量
unset:用于清除变量。不管这个变量是环境变量还是本地变量,它都可以清除。
当直接执行一个脚本的时候,其实是在一个子shell 环境运行的,即开启了一个子shell 来执行这个脚本,脚本执行完后该子shell 自动退出。
有没有办法在当前shell 中执行一个脚本呢?使用source 命令就可以让脚本在当前shell 中执行。
alias:可以用来自定义属于自己的系统命令,写入~/.bashrc 文件永久生效。
查看别名:
# alias
设置别名:
# alias mycom='echo hello;hostname'
# mycomm
hello
删除别名: unalias mycomm
别名还可以定义在/etc/bashrc中
示例:
[root@localhost mnt]# a=1 定义一个本地变量
[root@localhost mnt]# echo $a
1
[root@localhost mnt]# cat test16.sh
#!/bin/bash
echo $a
[root@localhost mnt]# sh test16.sh
[root@localhost mnt]# export a=1 ###用于把变量变成当前shell 和其子shell 的环境变量,存活期是当前的shell 及其子shell ,因此重新登陆以后,它所设定的环境变量就消失了。
[root@localhost mnt]# sh test16.sh
1
[root@localhost mnt]# logout
[root@localhost mnt]# sh test16.sh
重新登录后,变量a没有了,
那如何将本地变量永久化:
[root@localhost ~]# vim .bash_profile
修改:export PATH=$PATH:/mnt
添加一行:export a=1
重新登录后:
[root@localhost ~]# test16.sh
1
如果切换到student用户呢:
[student@localhost ~]$ test.sh
bash: test.sh: command not found...
[student@localhost ~]$ /mnt/test16.sh
/root/.bash_profile没有起作用,那怎么让同一变量在两个用户都生效:
vim /etc/profile
[root@localhost ~]# vim /etc/profile
export PATH=$PATH:/mnt
export a=2
[root@localhost ~]# source /etc/profile
[root@localhost ~]# su - sstuden^C
[root@localhost ~]# su - student
Last login: Mon Mar 6 19:16:48 EST 2017 on pts/1
[student@localhost ~]$ test16.sh
2
2.Linux 中环境变量的文件
当你进入系统的时候,linux 就会为你读入系统的环境变量,这些环境变量存放在什么地方,那就是环境变量的文件中。Linux 中有很多记载环境变量的文件,它们被系统读入是按照一定的顺序的。
1. /etc/profile :
此文件为系统的环境变量,它为每个用户设置环境信息,当用户第一次登录时,该文件被执行。并从/etc/profile.d 目录的配置文件中搜集shell 的设置。
这个文件,是任何用户登陆操作系统以后都会读取的文件(如果用户的shell 是csh 、tcsh 、zsh ,则不会读取此文件),用于获取系统的环境变量,只在登陆的时候读取一次。
2. /etc/bashrc :
在执行完/etc/profile 内容之后,如果用户的SHELL 运行的是bash ,那么接着就会执行此文件。另外,当每次一个新的bash shell 被打开时, 该文件被读取。
每个使用bash 的用户在登陆以后执行完/etc/profile 中内容以后都会执行此文件,在新开一个bash 的时候也会执行此文件。因此,如果你想让每个使用bash 的用户每新开一个bash 和每次登陆都执行某些操作,或者给他们定义一些新的环境变量,就可以在这个里面设置。
3. ~/.bash_profile :
每个用户都可使用该文件输入专用于自己使用的shell 信息。当用户登录时,该文件仅仅执行一次,默认情况下,它设置一些环境变量,执行用户的.bashrc 文件。
单个用户此文件的修改只会影响到他以后的每一次登陆系统。因此,可以在这里设置单个用户的特殊的环境变量或者特殊的操作,那么它在每次登陆的时候都会去获取这些新的环境变量或者做某些特殊的操作,但是仅仅在登陆时。
4. ~/.bashrc :
该文件包含专用于单个人的bash shell 的bash 信息,当登录时以及每次打开一个新的shell 时, 该该文件被读取。
单个用户此文件的修改会影响到他以后的每一次登陆系统和每一次新开一个bash 。因此,可以在这里设置单个用户的特殊的环境变量或者特殊的操作,那么每次它新登陆系统或者新开一个bash ,都会去获取相应的特殊的环境变量和特殊操作。
5. ~/.bash_logout :
当每次退出系统( 退出bash shell) 时, 执行该文件。
例1:
#vim test2.sh
#!/bin/bash
FNAME=westos
ONAME=linux
echo $FNAME
echo $ONAME
echo $FNAME_$ONAME
[root@localhost mnt]# sh test2.sh
westos
linux
linux #把FNAME_当成了一个变量,这个变量我们并没有定义,所以不显示可以将echo $FNAME_$ONAME改为echo ${FNAME}_$ONAME
例2:
#vim test3.sh
##!/bin/bash
TIME=date +%H:%M:%S
echo $TIME
[root@localhost mnt]# sh test2.sh
test2.sh: line 7: +%H:%M:%S: command not found
将TIME=date +%H:%M:%S改为TIME=$(date +%H:%M:%S)
练习1:找出mail为用户组的文件保存到/mnt并且以名字为mail.hh-mm-ss
#!/bin/bash
DIRNAME=mail.`date +%H-%M-%S`
mkdir /mnt/$DIRNAME
find / -group mail -exec cp -pr {} /mnt/$DIRNAME \;
三、流程控制语句
1.
for
do
...
done
例3:
#!/bin/bash
for NUM in {1..10}
do
echo $NUM
done
里面的for NUM in {1..10}还可以写成
for NUM in $( seq 1 2 10 )
for NUM in westos linux redhat
for ((i=1;i<=10;i++))
2.&& ||
语句1 && 语句2 || 语句3:当语句1正确执行就执行语句2,否则就执行语句3
例4:
[root@localhost mnt]# ls /etc/ll && echo ok || echo error
ls: cannot access /etc/ll: No such file or directory
error
若不想要错误信息可以&> /dev/null
练习2:如果1~10的主机可以ping通就显示ip is up,否则就显示ip is down
#!/bin/bash
for NUM in {181..190}
do
ping -c1 -w1 172.25.254.$NUM &> /dev/null && echo 172.25.254.$NUM is up || echo 172.25.254.$NUM is down
done
格式优化:
#!/bin/bash
for NUM in {181..190}
do
ping -c1 -w1 172.25.254.$NUM &> /dev/null && (
echo 172.25.254.$NUM is up ) || (
echo 172.25.254.$NUM is down )
done
练习3:文件username里面的内容
user1
user2
user3
文件passwdfile里面的内容
westos1
westos2
westos3
要求:
1.建立username里面的用户
2.赋予其密码,密码在passwdfile
3.如果两个文件行数不一致,就报错
4.如果用户存在,就提示信息nothing to do
#!/bin/bash
ULINE=`wc -l /mnt/username | cut -d' ' -f1`
PLINE=`wc -l /mnt/passwdfile | cut -d' ' -f1`
if [ "$ULINE" = $PLINE ]
then
for NUM in $( seq 1 $ULINE )
do
USERNAME=`sed -n ${NUM}p /mnt/username`
PASSWORD=`sed -n ${NUM}p /mnt/passwdfile`
id $USERNAME &> /dev/null && (
echo "$USERNAME has existed,nothing to do" ) || (
useradd $USERNAME
echo $PASSWORD | passwd --stdin $USERNAME )
done
else echo "the two file's lines is not same"
fi
3.while do break done
练习4:有一个文件,文件内容是
jack
tom
lee
mary
输出该文件,当为lee时,在后面追加brother
(1)用if
#!/bin/bash
LINE=`wc -l username | cut -d' ' -f 1`
for i in $( seq 1 $LINE )
do
if [ "`sed -n ${i}p username`" = "lee" ]
then echo lee brother
else echo `sed -n ${i}p username`
fi
done
(2)用&& ||
#!/bin/bash
LINE=`wc -l username | cut -d' ' -f 1`
for i in $( seq 1 $LINE )
do
[ "`sed -n ${i}p username`" = "lee" ] && echo $NAME brother || echo $NAME
done
$[数学表达式]:表示数学运算
+、-、*、/、**(幂运算)、%(求余)、+=、-=、++、--
示例:#echo $[1/2]
(( ... )):结构可以用来计算并测试算术表达式的结果. 退出状态将会与[ ... ]结构完全相反!还可应用到c风格的for,while循环语句,(( )) 中,所有的变量(加不加$无所谓)都是数值
():命令组,括号里的命令会在在字shell 中执行,在括号中的变量,由于是在子shell中,所以对于脚本剩下的部分是不可用的. 父进程, 也就是脚本本身, 将不能够读取在子进程中创建的变量, 也就是在子shell中创建的变量.
命令替换
命令替换是指将命令的标准输出作为值赋给某个变量,bash Shell定义了两种形式进行命令的替换,两种形式的语法格式如下:
方式一: `Linux命令`
方式二: $(Linux命令)
练习5:做一个10s的倒计时
#!/bin/bash
for ((i=10;i>0;i--))
do
echo -n countdown:$i
echo -ne ' \r'
sleep 1
done
练习6:做一个1分10s的倒计时
#!/bin/bash
M=1
for ((S=10;S>=0;S--))
do
echo -n "countdown:$M:$S"
echo -ne " \r"
sleep 1
while
[ "$S" = "0" -a "$M" -gt "0" ]
do
echo -n "$M:$S"
echo -ne " \r"
S=60
((M--))
done
done
三、脚本传参
$#:参数的总个数
$0:脚本名
$1:脚本名后的第一个参数
$@/$*:脚本的所有参数
read:交互式定义变量
例5:从命令行上读取一个值放到变量WORD中
#!/bin/bash
read -p "please me a word:" WORD1
read -p "please me a word:" -s WORD2
-s:输入时没有回显
退出状态:
命令成功执行完时,返回退出状态0,否则返回非零值
Linux命令完成时,将返回退出状态。成功完成程序时,将返回0的推出状态。这被bash
当作逻辑True值。非零退出状态通常表示发生了错误,并且被bash当作逻辑False值。
例如:grep的退出状态的含义:
0 – 在指定的文件中找到了模式
1 – 在指定的文件中未找到模式
>1 – 一些其他错误(无法打开文件、错误的搜索表达式等)
推出状态的值被存储在"?"中,可以使用以下命令查看:
# echo $?
条件判断:
1.test 表达式
2.[表达式]
[ -z 变量 ]:如果变量不是0
[ -n 变量 ]:如果变量是0
非零或零长度字符串运算符:test -{n|z} STRING
[root@server0 ~]# [ -n westos ]; echo $?
0
[root@server0 ~]# [ -z westos ]; echo $?
1
字符串比较运算符:=、!=
数字比较运算符:-eq、-ne、-lt、-le、-gt、-ge
文件状态运算符:test -{b|c|e|f|d|r|w|x|s|L} FILE/DIRECTORY
test –b File 文件存在并且是块设备文件
test –c File 文件存在并且是字符设备文件
test –d File 文件存在并且是目录
test –e File 文件存在
test –f File 文件存在并且是正规文件
test –g File 文件存在并且是设置了组ID
test –G File 文件存在并且属于有效组ID
test –h File 文件存在并且是一个符号链接(同-L)
test –k File 文件存在并且设置了sticky位
test –b File 文件存在并且是块设备文件
test –L File 文件存在并且是一个符号链接(同-h)
test –o File 文件存在并且属于有效用户ID
test –p File 文件存在并且是一个命名管道
test –r File 文件存在并且可读
test –s File 文件存在并且是一个套接字
test –t FD 文件描述符是在一个终端打开的
test –u File 文件存在并且设置了它的set-user-id位
test –w File 文件存在并且可写
test –x File 文件存在并且可执行
二进制文件运算符:-ef、-nt、-ot
test File1 –ef File2 两个文件具有同样的设备号和i结点号
test File1 –nt File2 文件1比文件2 新
test File1 –ot File2 文件1比文件2 旧
逻辑运算符:-o、-a、!、&&、||
[root@server0 bin]# [ 2 -gt 1 -a 1 -gt 2 ]; echo $?
1
[root@server0 bin]# [ 2 -gt 1 -o 1 -gt 2 ]; echo $?
0
[root@server0 bin]# [ ! 2 -gt 1 ]; echo $?
1
四、条件分支语句
1.
if
then
else
fi
if命令检查if后面的命令或列表的退出值。如果第一个命令评估为true/零,则运行then
之后的命令列表,直至任一else。如果第一个命令评估为false/非零,则运行else与fi之
间的命令列表(反向平写if,标记if块的结束)。
语法:if command; then command; command2; else command3; fi
示例:
if test “$USER” != 'root' ; then
echo you are not logged in as root
fi
if [ $(id -u) -lt 9 ] ; then
echo “The number $(id -u) is less than 9!”
fi
2.case
case语句 :它能够把变量的内容与多个模板进行匹配,再根据成功匹配的模板去决定应该执行哪
部分代码。
case "$1" in
start)
systemctl start $2
;;
stop)
systemctl stop $2
;;
reload|restart)
systemctl stop $2
systemctl start $2
;;
*)
echo "Usage: $0 (start|stop|restart|reload)"
;;
esac
五、expect语句
expect:shell中expect实现自动应答脚本:
实验:
1.写问题
#vim ask
#chmod +x /mnt/ask
2.安装expect
#yum install expect
3.写自动应答脚本
#!/usr/bin/expect
这一行告诉操作系统脚本里的代码使用那一个shell来执行。
set timeout 10
设置后面所有的expect命令的等待响应的超时时间,单位为秒。
spawn talk
spawn是expect的内部命令,作用是给后面的shell指令加个壳,用来传递交互指令。
expect "who"
判断上次输出结果里是否包含“who”的字符串,如果有则立即返回,否则等待超时时间后返回。
send "westos\n"
执行交互动作,相当于手工输入"westos"。
expect eof
作用是在输出中搜索文件结束符,如果没有这一行,脚本会立即退出,得不到正确结果。
interact
执行完成后保持交互状态,把控制权交给控制台,这个时候就可以手工操作了。否则退出登录。
$argv 参数数组
expect脚本可以接受从bash传递过来的参数.可以使用[lindex $argv n]获得,n从0开始,分别表示第
一个,第二个,第三个....参数。
练习7:写一个自动登录另一ip主机的自动应答脚本
/mnt/problem
#!/bin/bash
echo username
read USER
echo ipaddress
read IP
ssh ${USER}@$IP
read PASSWORD
/mnt/test15.exp
#!/bin/expect ##特别注意不要写成/bin/bash,否则错误在下
set USER [ lindex $argv 0 ]
set IP [ lindex $argv 1 ]
set PASSWORD [ lindex $argv 2 ]
spawn /mnt/problem
expect {
"username" { send "$USER\n"; exp_continue }
"ipaddress" { send "$IP\n"; exp_continue }
"password" { send "$PASSWORD\n"; exp_continue }
interact
}
错误:
/mnt/test15.exp: line 6: spawn: command not found
couldn't read file "{": no such file or directory
/mnt/test15.exp: line 8: username: command not found
六、函数
使用函数,用来简化脚本
pathmunge () {
if [ "$2" = "after" ] ; then
PATH=$PATH:$1
else
PATH=$1:$PATH
fi
}
...
if [ "$EUID" = "0" ]; then
pathmunge /usr/sbin
pathmunge /usr/local/sbin
else
pathmunge /usr/local/sbin after
pathmunge /usr/sbin after
fi