主要从事Linux,云原生架构改造,服务网格,ELK,python,golang等相关技术。
分类: LINUX
2015-05-09 14:17:45
音符~Linux
Shell编程学习笔记
1.对shell的介绍
1)什么是shell?
Shell它是一个命令解释器,负责直接与用户对话,把用户的输入解释给操作系统,并 处理各种各样的操作系统的输出结果,输出到屏幕返回给用户。这种对话方式可以是交 互的方式(从键盘输入命令,可以立即得到shell的回应),或非交互(脚本)的方式。
2)什么是shell脚本?
当命令或语句不在命令行执行,而是通过一个程序文件执行时,该程序就被称为shell脚本或shell程序,shell程序很类似DOS系统下的批处理程序(*.bat)。用户可以在shell脚本中桥如一系列的命令或命令语句。这些命令、变量和流程控制语句等有机的结合起来就形成了一个强大的shell脚本。
范例:1清空/var/log/messages日志文件的简单命令
我们使用脚本log.sh进行操作
#!/bin/bash
#清除日志脚本,版本2
LOG_DIR=/var/log
ROOT_UID=0 #$UID为0的时候,用户才具有root用户的权限
#要使用root用户来运行.
if [ "$UID" -ne "$ROOT_UID" ]
then
echo "Must be root to run this script."
exit 1
fi
cd $LOG_DIR || {
echo "Cannot change to necessary directory." >&2
exit 1
}
cat /dev/null > messages && echo "Logs cleaned up."
exit 0 //这里退出之前返回0表示成功。返回1表示失败。
[root@server2 log]# sh log.sh
Logs cleaned up.
下面我们给出几种不同的清空日志的方法
第一种
[root@server2 ~]#echo " " > test.log
第二种
[root@server2 ~]#echo > test.log
第三种
[root@server2 ~]#cat /dev/null > test.log
应用场合:保留文件,清空内容
运维开发人员常用的脚本语言:
Php(主要是做网页程序,也是脚本语言)
Perl
Python(现在已经成为主流)
Linux默认的shell是bash
[root@server2 log]# echo $SHELL //我们可以使用这个命令进行查看
/bin/bash
3)写脚本应该注意的几点:
a)要写注释
b)在执行的时候可以使用全路径进行执行或在当前路径执行这种情况需要有-x执行权限
c)用source filename和. filename来执行
2.一些简单的操作
#cat > test.sh
echo hello,we are yanghaiying.
1)脚本执行的方法:
ctrl+D/ctrl+c可以直接保存,但是这样只能使用shell解释器去执行shell程序
如果想要执行,我们需要使用chmod +x test.sh
#source test.sh / #. test.sh
a)这两种做法我们都可以进行test.sh执行,可以直接调用脚本里面的函数,
b)但是如果这个脚本没有可执行权限的时候,使用sh不能直接调用脚本里面的函数、变量等等,所以一般情况下推荐使用上面两种方法。
eg:
[root@server2 ~]# cat >test.sh
user=`whoami`
[root@server2 ~]# sh test.sh
[root@server2 ~]# echo $user
[root@server2 ~]# source test.sh 或者 . test.sh
[root@server2 ~]# echo $user
root
2)脚本的书写规范
a)指定解释器,加版权等信息
#!/bin/bash
# Date 2015-01-13
# Author:Created by hy
# Mail:1020659371@qq.com
# Function:This scripts function is MySQL backup
# V2.1
b)不要使用中文注释
c)脚本以.sh为扩展名,例:script-name.sh
d)成对的内容,一次性写出来,防止遗漏,如:{},[],'',``,""等
e)[]中括号两端有空格,例如:[ abc ]
f)在写一些程序的时候我们要将一些语句块的前后首先对应写出来,例如:
if语句格式一次完成:
if 条件
then
语句(一般是语法写全了后中间再加语句)
fi
for循环格式一次完成:
for n in list
do
语句(一般是语法写全了后中间再加语句)
done
提示:while和until,case等语句也是一样。
g)学会缩进,让代码更易读
多行同时缩进的操作,ctrl+v-->选定要缩进的行-->按TAB(如果进行注释按#)
-->按Esc
3.环境变量
3.1全局变量的设置
在 /etc/profile或在/etc/profile.d/*.sh
例如:
TMOUT=3600:可以设置推出前等待超时的秒数
UID=0:当前用户的UID
HISTFILESIZE=50:历史文件包含的最大行数
HISTSIZE=50:记录在命令行历史文件中的命令行数
3.2自定义环境变量(全局变量)
设置环境变量:如果想设置环境变量,就要在给定变量赋值之后或设置变量时使用export命令。带-x选项的declare内置命令也可以完成同样的功能。
(注意:输出变量时不要在变量名前面加$)
--
-f
-n
-p
格式:
1)export 变量名=value
2)变量名=value;export 变量名
3)declare -x 变量名=value
提示:以上为三种设置环境变量的方法
例如:
export NAME=hy
declare -x NAME=hy
NAME=hy;export NAME
eg:
[root@server2 ~]# export NAME=hy
[root@server2 ~]# echo $NAME
hy
[root@server2 ~]# declare -x name=hy
[root@server2 ~]# echo $name
hy
如何取消环境变量的定义:unset 变量名
3.3局部变量
变量名='value'
变量名="value"
shell中变量名的要求:一般是字母,数字,下划线组成。字母开头。hy,hy123,hy_trip.
eg:
[root@server2 ~]# a=192.168.2.2
[root@server2 ~]# b='192.168.2.2'
[root@server2 ~]# c="192.168.2.2"
[root@server2 ~]# echo "a=$a"
a=192.168.2.2
[root@server2 ~]# echo "b=$b"
b=192.168.2.2
[root@server2 ~]# echo "c=$c"
c=192.168.2.2
[root@server2 ~]# echo "c=${c}"
c=192.168.2.2
[root@server2 ~]# a=192.168.2.2-$a
[root@server2 ~]# b='192.168.2.2-$a'
[root@server2 ~]# c="192.168.2.2-$a"
[root@server2 ~]# echo "a=$a"
a=192.168.2.2-192.168.2.2
[root@server2 ~]# echo "b=$b"
b=192.168.2.2-$a
[root@server2 ~]# echo "c=$c"
c=192.168.2.2-192.168.2.2-192.168.2.2
提示:
第一种定义a变量的方式是直接定义变量内容,内容一般为简单连续的数字、字符串、路径名等。
第二种定义b变量的方式是通过单引号定义变量。这个方式的特点是:输出变量时引号里是什么就输出什么,即使内容中有变量也会把变量名原样输出。此法比较适合于定义显示春字符串。
第三中定义c变量方式是通过双引号定义变量。这种方式的特点是输出变量时引号里的内容会经解析后输出该变量内容,而不是把引号中变量名原样输出,适合于字符串附带有变量的内容的定义。
特殊的:在awk里面刚好相反
[root@server2 ~]# awk 'BEGIN {print '$a'}'
192.1680.2-191.9680.20.2
[root@server2 ~]# awk 'BEGIN {print "$a"}'
$a
3.4全局变量和局部变量名的定义
a.脚本中的全局变量定义,如HY_HOME或HYHOME,在变量使用{}将变量括起或"${HY_HOME}" (当然这个不是必须的,这只是系统的一个规范)
b.脚本中局部变量定义:存在于脚本函数(function)中的变量称为局部变量,要以local方式进行声明,使之只在本函数作用域内有效,防止变量在函数中的命名与变量外部程序中变量重名造成程序异常。下面是函数的变量定义例子:
function TestFunc()
{
local i
for((i=0;i
do
echo 'do something'
done
}
3.5
a.操作系统函数库脚本内容局部函数变量截取例子:多学习模仿操作系统自带的这个脚本/etc/init.d/functions函数脚本定义的思路(要是能把这个里面的读懂,知道每一各字符是什么意思就很不简单了)
b.把一个命令作为变量
提示:
1)"cmd=`ls`"注意命令变量前后的字符``(为键盘tab键上面的那个,不是单引号)
2)在变量名前加$,可以取得此变量的值,使用echo命令可以显示变量的值,$A或${A}的写法不同,但功能一样的,推荐使用后者的语法或"${A}"的用法。
3)$(WEEK)day若变量和其他字符组成新的变量就必须给变量加上大括号{}。
4)养成将所有字符串变量用双引号括起来使用的习惯,将会减少很多编程时遇到的怪异的错误。具体使用方法如:"$A"或"${A}"的用法。
[root@server2 ~]# cmd=`date +%F` 这里要特别注意,“``”不是单引号
[root@server2 ~]# echo $cmd
2015-01-14
[root@server2 ~]# cmd=$(date +%F)
[root@server2 ~]# echo $cmd
2015-01-14
[root@server2 ~]# tar zcf etc_${cmd}_hy.tar.gz /etc $A和${A}写法不同,但功能相同,推荐使用后者的语法或"${A}"的用法,这里我们就可以体现出使用后者不会引起变量名的混淆
[root@server2 ~]# ls
etc_2015-01-14_hy.tar.gz
[root@server2 ~]# tar zcf etc_$cmd_hy.tar.gz /etc
[root@server2 ~]# ll
总用量 9744
-rw-r--r-- 1 root root 4974083 1月 14 00:13 etc_2015-01-14_hy.tar.gz
-rw-r--r-- 1 root root 4974083 1月 14 00:17 etc_.tar.gz
这里我们看到如果不使用{}的后果
这里我们在看一个小例子,使用机器名(日期/软件名)打包:
[root@server2 ~]# uname -n
server2.example.com
[root@server2 ~]# H=$(uname -n)
[root@server2 ~]# echo $H
server2.example.com
[root@server2 ~]# tar zcf $H.tar.gz /etc/services
[root@server2 ~]# ll
总用量 9872
-rw-r--r-- 1 root root 4974083 1月 14 00:13 etc_2015-01-14_hy.tar.gz
-rw-r--r-- 1 root root 4974083 1月 14 00:17 etc_.tar.gz
-rw-r--r-- 1 root root 127319 1月 14 00:44 server2.example.com.tar.gz
3.6 shell特殊变量
1)位置变量
$0 获取当前执行的shell脚本的文件名,包括路径。
#!/bin/bash
echo $0
[root@server2 ~]# sh $0 的执行结果是0.sh
[root@server2 ~]# sh /root/0.sh 的执行结果是/root/0.sh,会打印出完整的路径
[root@server2 ~]#vim 0.sh //这个脚本显示就更加清晰了
#!/bin/bash
dirname $0
basename $0
[root@server2 ~]# sh /root/0.sh
/root
0.sh
#seq -s : 10 //-s是指定分隔字符
1:2:3:4:5:6:7:8:9:10
# seq -s " $" 10
1 $2 $3 $4 $5 $6 $7 $8 $9 $10
测试$的用法
#!/bin/bash
echo $1 $2 $3 $4 $5 $6 $7 $8 $9 ${10}
echo $# //这里是计算命令行参数的个数
echo $* //输出所有字符
[root@server2 ~]# sh n.sh 1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
10
1 2 3 4 5 6 7 8 9 10
[root@server2 ~]# sh n.sh "1 2 3 4 5 6 7 8 9 10"
1 2 3 4 5 6 7 8 9 10 //这里使用""其实只是一个字符串,也就是输出$1,所
1 1 2 3 4 5 6 7 8 9 10 //所以$#的结果是1
$1的详细说名
看下面脚本
#See how we were called
case "$1" in 这里的$1指从键盘输入的第二个字段,$0是第一个字段,然后进行下面的判断
start)
start
;;
stop)
stop
;;
status)
status portmap
;;
restart | reload)
restart
;;
condrestart)
[ -f /var/lock/subsys/portmap ] && restart || :
;;
*)
echo $"Usage: $0 (start|stop|status|restart|reload|condrestart)"
exit 1
esac
exit $?
[root@hy shell_基础_01]# /etc/init.d/portmap //这里我们只是输入了$0,$1为空
Usage: /etc/init.d/portmap (start|stop|status|restart|reload|condrestart)
[root@hy shell_基础_01]#
2)进程状态变量
$$ 获取当前shell的进程号(PID)
$! 执行上一个指令的PID
$? 获取执行上一个指令的返回值(0为成功,非零为失败) //这个变量很常用。
$_ 在此之前执行的命令脚本的最后一个参数
eg:
[root@server2 ~]# hy
-bash: hy: command not found
[root@server2 ~]# echo $?
127
[root@server2 ~]# pwd
/root
[root@server2 ~]# echo $?
0
$?返回值我们可以参考下面
1-125 表示运行失败,脚本命令、系统命令错误或参数传递错误
126 找到命令了,但是无法执行
127 未找到要运行的命令
>128 命令被系统强制结束
3)对$?的说明
#!/bin/bash
cd /etc
tar zcf services.tar.gz ./services >&/dev/null
[ $? -eq 0 ] && echo ok
如果我们在命令行执行会输出ok,如果我们不加打包时候的参数,将没有输出结果
4) $*和$@的区别例子
l $* 将所有的命令行所有参数视为单个字符串,等同于“$1$2$3”
l $@ 将命令行每个参数视为单独的字符串,等同于“$1”,“$2”,“$3”。这是将参数传递给其他程序的最佳方式,因为他会保留所有内嵌在每个参数里的任何空白。
实例:
[root@server2 ~]#set -- “I am” handsome hy.
[root@server2 ~]#echo $#
3
[root@server2 ~]#for i in $*;do echo $i;done
I
am
handsome
hy.
[root@server2 ~]#for i in $@;do echo $i;done
I
am
handsome
hy.
[root@server2 ~]#for i;do echo $i;done //去掉in 变量列表,相当于in “$@”
I am
handsome
hy.
[root@server2 ~]#for i in “$*”;do echo $i;done
I am handsome hy. //这里不是说输出到一行,而是将所有的字符当成一个字符串
5)Shift的使用
[root@server2 ~]#shift //该命令是将字符进行退格处理
[root@server2 ~]#echo $1
handsome
[root@server2 ~]#echo $2
hy.
[root@server2 ~]#echo $3 //这时我们可以看到$3的输出结果为空
可以less /usr/bin/ssh-copy-id查看这个shell文件,他里面就用到了shift
网上查看eval的使用:http://oldboy.blog.51cto.com/2561410/1175971
3.7对变量的操作
[root@server2 ~]#hy=”I am hy.”
[root@server2 ~]#echo ${#hy}
8
[root@server2 ~]#echo $hy | wc -m
9
截取hy变量字符串从第2个字符之后开始取,默认取后面字符的全部,第二个字符不包含在内。也可理解为删除前面的多少个字符。例:
[root@server2 ~]#echo ${hy:2}
am hy.
[root@server2 ~]#echo ${hy:2:2}
am
[root@server2 ~]#echo ${hy} | cut -c 1-4 //也可以使用cut来进行截取
I am
[root@server2 ~]#echo ${hy} | cut -c 3-4
am
从变量$hy开头开始删除最短匹配“I am”子串
[root@server2 ~]#echo ${hy#I am}
hy.
[root@server2 ~]#echo ${hy##I am h}
y.
从变量$hy结尾开始删除最短匹配
[root@server2 ~]#echo ${hy%hy.}
I am
[root@server2 ~]#echo ${hy%I am} //这样就不行
I am hy.
从变量$hy结尾开始删除最长匹配
[root@server2 ~]#echo ${hy%%hy}
使用etiantian字符串,来代替变量$hy第一个匹配的hy字符串
[root@server2 ~]#echo ${hy/I am/you are}
you are hy.
使用etiantian字符串,来代替变量$hy结尾开始匹配的hy字符串
[root@server2 ~]#echo ${hy/%hy/server}
I am server
使用 he is 字符串,来代替从变量$hy开头开始匹配I am字符串
[root@server2 ~]#echo ${hy/#I am/he is}
he is hy.
生产场景用法实例:
1)变量结尾删除生产实践:
功能描述如下表:
${string%substring} 从变量$string结尾开始删除最短匹配$substring子串
批量文件改名案例实践:
问题1:把下面所有文件的文件名中的finished内容去掉
首先我们创建几个文件:
[root@server2 ~]#for i in $(seq 4);do touch stu_20150117_”$i”_finished.jpg;done
[root@server2 ~]#ll 我们可以看到这四个文件被创建
[root@server2 ~]#cat file
stu_20150117_1_finished.jpg
stu_20150117_2_finished.jpg
stu_20150117_3_finished.jpg
stu_20150117_4_finished.jpg
[root@server2 ~]#for i in `cat file`;done mv $i `echo ${i/%finished.jpg/.jpg}`;done
[root@server2 ~]#ls -1
stu_20150117_1_.jpg
stu_20150117_2_.jpg
stu_20150117_3_.jpg
stu_20150117_4_.jpg
[root@server2 ~]#test=”stu_20150117_1_finished.jpg”
[root@server2 ~]#echo ${test%finished*}.jpg
stu_20150117_1_.jpg
注:这两行语句是做一个测试,这是处理这类批量处理问题的思路,先从一个出发
当然这个也可以使用ls加awk来实现
#ls | grep stu | awk -F “finished” ‘{print $1$2}’ 可以达到同样的效果
#ls | grep stu | awk -F “finished” ‘{print “mv “$0” “$1$2” “}’ | /bin/bash
#rename finished “” * 这两个也可以完成所需的操作
其他变量的替换:
变量替换表:
[root@server2 ~]#result=${test:-UNSET} 但有些脚本(像httpd启动脚本)里面是去掉”:”号也可以,这里我们可以在/etc/init.d/里面的脚本对比着看
[root@server2 ~]#echo $result
unset
[root@server2 ~]#echo $test
这里是空
[root@server2 ~]#test=”hy”
[root@server2 ~]#result=${test:-UNSET} //我们可以看到result被重新赋值
[root@server2 ~]#echo $result
hy
[root@server2 ~]#echo $test
hy
[root@server2 ~]#unset test //这部操作是取消test的值
[root@server2 ~]#unset result
[root@server2 ~]#result=${test:=UNSET}
[root@server2 ~]#echo $result
UNSET
[root@server2 ~]#echo $test
UNSET //这时我们发现test不为空
简单说明一下后面两个的测试结果:
${value:? “not define”}是用来捕捉由于变量未定义而导致的错误,如:”not defined”.
[root@server2 ~]#echo ${value:? “not define”}
-bash:value: not defined
[root@server2 ~]#value=1
[root@server2 ~]#echo ${value:? ”not defined”}
1
${value:+word}
[root@server2 ~]#r=${value:+1}
[root@server2 ~]#echo $r
是空的
[root@server2 ~]#value=hy
[root@server2 ~]#r=${value:+1}
[root@server2 ~]#echo $r
1
以上有什么用途那?有时候我们可能定期需要清理磁盘缓存信息我们就可以使用这个定义好的变量直接指定
[root@server2 ~]#path1=”/tmp”
[root@server2 ~]#rm -fr ${path1-/tmp} 这是防止我们可能没有保存path1的值,就很危险,有可能他会从根开始删除,这样做他就会去直接删除/tmp里面的文件
我们可以将其写在一个脚本里面看看执行结果:
d.sh
#!/bin/bash
path=/mnt/backup
find ${path:=/tmp/} -name “*.tar.gz” -type f | xargs rm -f
[root@server2 ~]#sh -x d.sh //跟踪执行
+ path=/mnt/backup
+ xargs rm -f
+ find /mnt/backup -name ‘*.tar.gz’ -type f
[root@server2 ~]#vim d.sh //我们将第一行注释掉
#!/bin/bash
#path=/mnt/backup
find ${Path:=/tmp/} -name “*.tar.gz” -type f | xargs rm -f
[root@server2 ~]#sh -x d.sh //跟踪执行结果,他会在/tmp这个目录里面去找
+ xargs rm -f
+ find /tmp -name ‘*.tar.gz’ -type f
邮箱: