生产环境中的 Shell 脚本,通常被设置为定时任务来完成自动调度。这里介绍 bash(Bourne Again Shell) 语法,以及常用命令和软件。
脚本如何执行 指定解释器 一个规范的 Shell 脚本会在第一行使用 #!
指定由哪个程序(解释器)来执行脚本中的内容,当脚本执行时,就会加载相关环境配置(通常是 non-login shell 的 ~/.bashrc
)。
指定 bash 来执行脚本:
执行顺序 Shell 脚本执行时会先加载环境变量,然后从上到下,从左至右分析与运行脚本中的命令。当读取到一个换行符 CR
,就尝试开始运行该行的命令,如果编写的一行命令内容过长,可以在行末用 \
表示换行后的内容仍与当前行为同一句命令。当脚本执行中遇到子脚本时,会执行子脚本的内容,完成后再返回父脚本继续执行后续的命令。
变量 Shell 中的变量和其它编程语言一样,指向一段内存空间。
定义变量并赋值 变量名由一系列字母、数字和下划线组成,不能使用空白字符,不能以数字开头,区分大小写。通常在脚本中使用大写字母命名环境变量,使用驼峰命名法或小写字母命名其他变量。
Shell 是弱类型的语言,使用前不必先声明。定义一些变量并赋值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 CURRENT_YEAR=2021 TEXT=Hello MY_NAME="sannaha" var= readonly MY_BLOG='sannaha.moe' MY_BLOG='sannaha.com' -bash: MY_BLOG: readonly variable unset MY_NAMEunset MY_BLOG-bash: unset : MY_BLOG: cannot unset : readonly variable for filelist in $(ls ~); do echo $filelist ; done ; echo "filelist=$filelist " ;hadoop kafka mysql spark filelist=spark
使用变量 1 2 3 4 5 echo $MY_NAME echo "my blog is ${MY_NAME} .moe" my blog is sannaha.moe
变量作用域 Shell 变量的作用域可以分为三种:
环境变量 :可以在当前 Shell 进程以及子进程中使用;
用户变量 :可以在当前 Shell 进程中使用;
局部变量 :只能在函数内定义和使用。
环境变量 Shell 维护着一组环境变量,用来记录特定的系统信息。比如系统的名称、登录到系统上的用户名、用户的系统ID(也称为UID)、用户的默认主目录以及 Shell 查找程序的搜索路径。可以用 set
命令来显示一份完整的当前环境变量列表:
1 2 3 4 5 6 7 8 $ set BASH=/bin/bash HOME=/home/sannaha HOSTNAME=cdh1 ID=1006 LINES=24 JAVA_HOME=/usr/java/jdk1.8.0_141-cloudera ...
使用 export
命令将变量“导出”,那么这个变量就成为了环境变量 ,在当前 Shell 和所有子 Shell 中都有效。
两个没有父子关系的 Shell 进程是不能传递环境变量的,并且环境变量只能向下传递而不能向上传递。示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $ g=global $ export e=environment $ bash $ echo $g $ echo $e environment $ export se=subshellEnvironment $ exit exit $ echo $se
用户变量 全局变量是指变量在当前整个 Shell 进程中都有效。每个 Shell 进程都有自己的作用域,彼此之间互不影响。在 Shell 中定义的变量,默认就是全局变量。
全局变量的作用范围是当前的 Shell 进程,而不是当前的 Shell 脚本文件,它们是不同的概念。打开多个 Shell 终端窗口就创建了多个 Shell 进程,每个 Shell 进程都是独立的,拥有不同的进程 ID。在一个 Shell 进程中可以使用 source
命令执行多个 Shell 脚本文件,此时全局变量在这些脚本文件中都有效。示例:
编写 a.sh
:
a.sh 1 2 3 #!/bin/bash echo $a b='blog'
编写 b.sh
:
b.sh
执行脚本文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ a='sannaha' $ ./a.sh $ ./b.sh $ source a.sh sannaha $ source b.sh blog $ . ./a.sh sannaha $ . ./b.sh blog
sh demo.sh
:建立一个子 Shell 并在子 Shell 中执行脚本中的命令,子 Shell 继承父 Shell 的环境变量 ,但不继承父 Shell 的全局变量,同时子 Shell 中创建的变量和对变量做的改动不会带回父 Shell。
source demo.sh
:,是读取脚本中的命令并在当前 Shell 中执行,没有建立子 Shell。脚本中所有创建、改动变量的操作都作用在当前 Shell 中。
./demo.sh
:只有用 chmod +x
为脚本 demo.sh
添加执行权限后才能以这种方式执行脚本,当脚本具有可执行权限时,sh demo.sh
和 ./demo.sh
两种方式执行脚本是没有区别的。
. demo.sh
:与 source demo.sh
一致。
局部变量 与 Java 等编程语言不同,在 Shell 函数中定义的变量默认也是全局变量 ,它和在函数外部定义变量拥有一样的效果。示例:
varTest.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/bin/bash username='sannaha' function func () { local text='hi' local username='admin' echo $text echo $username } func echo $text echo $username
输出结果:
1 2 3 4 5 $ ./varTest.sh hi admin sannaha
如果想要变量的作用域仅限于函数内部,可以在定义变量时加上 local
命令,此时该变量就成了局部变量。示例:
1 2 3 4 5 6 7 8 9 #!/bin/bash function func () { local a=99 } func echo $a
输出结果为空,表明变量 a 在函数外部无效,是一个局部变量。
变量内容替换
${变量#匹配规则}
:从头匹配,最短删除;
${变量##匹配规则}
:从头匹配,最长删除;
${变量%匹配规则}
,从尾匹配,最短删除;
${变量%%匹配规则}
,从尾匹配,最长删除;
${变量/旧字符串/新字符串}
:用新字符串替换变量中第一个旧字符串;
${变量//旧字符串/新字符串}
,用新字符串替换变量中所有旧字符串。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 FILE_PATH=/dir1/dir2/dir3/my.file.txt $ echo ${FILE_PATH#*/} dir1/dir2/dir3/my.file.txt $ echo ${FILE_PATH##*/} my.file.txt $ echo ${FILE_PATH%/*} /dir1/dir2/dir3 $ echo ${FILE_PATH%%/*} 结果为空 $ echo ${FILE_PATH/dir/mydir} /mydir1/dir2/dir3/my.file.txt $ echo ${FILE_PATH//dir/mydir} /mydir1/mydir2/mydir3/my.file.txt
变量状态测试 由变量的状态决定变量的值,变量状态包括未设定 unset
、空值 null
、非空 non-null
。变量测试其实就是对判断和赋值语句的简化,可以简化脚本的编写,但需要记忆。变量测试表:
变量置换方式
变量y没有设置
变量y为空值
变量y设置有值
x=${y-新值}
x=新值
x=$y
x=$y
x=${y:-新值}
x=新值
x=新值
x=$y
x=${y+新值}
x为空
x=新值
x=新值
x=${y:+新值}
x为空
x为空
x=新值
x=${y=新值}
x=新值,y=新值
x=$y,y值不变
x=$y,y值不变
x=${y:=新值}
x=新值,y=新值
x=新值,y=新值
x=$y,y值不变
x=${y?新值}
新值输出到标准错误输出
x=$y
x=$y
x=${y:?新值}
新值输出到标准错误输出
新值输出到标准错误输出
x=$y
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 $ unset var;echo ${var-sannaha} sannaha $ var=;echo ${var-sannaha} $ var=HelloWorld;echo ${var-sannaha} HelloWorld $ unset var;echo ${var:-sannaha} sannaha $ var=;echo ${var:-sannaha} $ var=HelloWorld;echo ${var:-sannaha} HelloWorld $ unset var;echo ${var?sannaha} -bash: var: sannaha $ var=;echo ${var?sannaha} $ var=HelloWorld;echo ${var?sannaha} HelloWorld $ unset var;echo ${var?sannaha} -bash: var: sannaha $ var=;echo ${var:?sannaha} -bash: var: sannaha $ var=HelloWorld;echo ${var:?sannaha} HelloWorld
变量长度 1 2 3 $ myname=sannaha; echo ${#myname} 7
变量运算 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 $ a=5;b=7;c=2; $ echo $a +$b *$c 5+7*2 $ echo $(($a +$b *$c )) 19 $ echo $((a+b+c)) 14 $ echo $(((a+b)/c)) 6 $ echo $((a&b)) 5 $ echo $((a|b)) 7 $ echo $((a^b)) 2 $ echo $((a==b)) 0 $ echo $((!((a==b)))) 1
其他 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 ${#var} $ a=5;b=7;c=2; $ echo $a +$b +$c 5+7+2 $ echo $((a+b+c)) 14 $ echo $(($a +$b +$c )) 14 $ echo $(((a+b)/c)) 6 在 $(( )) 中的变量名称,可于其前面加 $ 符号来替换,也可以不用,如: $(( $a + $b * $c )) 也可得到 19 的结果 此外,$(( )) 还可作不同进位(如二进制、八进位、十六进制)作运算呢,只是,输出结果皆为十进制而已: echo $((16#2a )) 结果为 42 (16进位转十进制)(())的用途: 事实上,单纯用 (( )) 也可重定义变量值,或作 testing: a=5; ((a++)) 可将 $a 重定义为 6 a=5; ((a--)) 则为 a=4 a=5; b=7; ((a < b)) 会得到 0 (true ) 的返回值。 常见的用于 (( )) 的测试符号有如下这些: <:小于 >:大于 <=:小于或等于 >=:大于或等于 ==:等于 !=:不等于 [root@05d764353843 bigdata] [root@05d764353843 bigdata] 0 [root@05d764353843 bigdata] 1 [root@05d764353843 bigdata] 0
字符串 Shell 脚本中常用的数据类型除了数字就是字符串了。字符串可以用单引号或双引号标识,也可以不用引号。
单引号 1 2 3 $ author='SANNAHA' $ echo $author SANNAHA
单引号字符串的限制:
单引号里的任何字符都会原样输出 ,单引号字符串中的变量是无效的;
单引号字串中不能出现单独一个的单引号 (对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用。
双引号 1 2 3 4 $ description="Hi I'm $author ! My blog is \"$author .moe\"\n" $ echo -e $description Hi I'm SANNAHA! My blog is "SANNAHA.moe"
双引号字符串的特点:
不用引号 1 2 3 $ simpleStr=HelloWorld $ echo $simpleStr HelloWorld
字符串拼接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 $ title="Hi " $author '!' $ echo $title Hi SANNAHA! $ title1="Hi " $author "!" -bash: !: event not found $ title2="Hi " $author ! $ echo $title2 Hi SANNAHA! $ title2="Hi " $author "\!" $ echo $title2 Hi SANNAHA\! $ vi stringTest.sh author=SANNAHA title1="Hi " $author "!" echo $title1 $ ./stringTest.sh Hi SANNAHA!
提取子字符串 1 2 3 4 $ longStr="my blog is sannaha.moe" $ echo ${longStr:11:7} sannaha
查找子字符串 1 2 3 4 $ longStr="my blog is sannaha.moe" $ echo `expr index "$longStr " bg ` 4
命令替换 Shell 脚本可以从命令输出中提取信息,并将其赋给变量。有两种方法可以将命令输出赋给变量:
示例:
编写脚本 printDate.sh
:
printDate.sh 1 2 3 4 5 6 7 8 #!/bin/bash year=年 month=月 day=日 today=`date -d "-0 day" +%Y$year %m$month %d$day ` yesterday=$(date -d "-1 days" +%Y$year %m$month %d$day ) echo "today is $today " echo "yesterday is $yesterday "
运行脚本:
1 2 3 $ ./printDate.sh today is 2021年08月11日 yesterday is 2021年08月10日
函数 1 2 3 4 5 6 [function ] funname () { action; [return int;] }
所有函数必须先定义再使用,使用函数名即可调用函数。
定义函数时可以不带 function
,只有 fun(){}
即可。
函数可以通过 return
返回运行结果,数值为 0
~ 255
,如果不加 return
将以最后一条命令运行结果作为返回值。
函数返回值可以在调用该函数后通过 $?
来获得。$?
仅对其上一条指令负责,一旦函数返回后执行了其他命令,那么函数的返回值将不再能通过 $?
获得。
调用函数时可以向其传递参数。在函数体内部可以通过 $n
的形式来获取参数的值,$1
表示第一个参数,$2
表示第二个参数……当 n>=10
时,需要使用 ${n}
获取参数。此外,还有几个特殊字符用来处理参数:
字符
说明
$#
传递到脚本或函数的参数个数
$*
以一个单字符串显示所有向脚本传递的参数
$$
脚本运行的当前进程ID号
$!
后台运行的最后一个进程的ID号
$@
与$*相同,但是使用时加引号,并在引号中返回每个参数。
$-
显示Shell使用的当前选项,与set命令功能相同。
$?
显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
条件判断 if语句 Shell 支持使用 if
进行条件判断:
1 2 3 4 5 6 7 8 if COMMAND; then COMMANDs elif COMMAND; then COMMANDs else COMMANDs fi
Shell 会按顺序执行 if
语句:
首先运行 if
后面的命令,比如条件测试;
然后判断命令的退出状态码,如果是 0
(即命令运行成功),则会执行该条件 then
后面对应的命令,否则会跳过这些命令;
如果有 elif
会再次进行判断。只有第一个退出状态码为 0
的条件对应的命令会被执行;
如果所有判断条件的退出状态码都不为 0
,则执行 else
后面的命令。
示例:
编写脚本 ifTest.sh
:
1 2 3 4 5 6 7 8 9 10 11 #!/bin/bash user1=$1 user2=$2 if grep ^${user1} /etc/passwd; then echo "${user1} 是Linux用户" elif grep ^${user2} /etc/passwd; then echo "${user1} 不是Linux用户,${user2} 是Linux用户" else echo "${user1} 和${user2} 都不是Linux用户" fi
运行脚本:
1 2 3 ./ifTest.sh SANNAHA root root:x:0:0:root:/root:/bin/zsh SANNAHA不是Linux用户,root是Linux用户
case语句 case
语句为多分支选择语句,匹配一个值或一个模式,如果匹配成功,执行相匹配的命令:
1 2 3 4 5 6 7 8 9 10 11 12 case 值 in 模式1) command ;; 模式2|模式3) command ;; *) command ;; esac
说明:
用值去匹配的每一个分支中的模式,值可以为变量或常数,分支以 )
结束;
一个分支可以由多个用 |
分隔的模式组成;
一旦匹配到某一模式后,开始执行对应的所有命令直至 ;;
,;;
相当于其他语言中的 break
,执行完命令后不再匹配其他模式。
如果上面的模式都不匹配,最后可以使用 *
进行捕获,执行对应的命令。
示例:
编写脚本 caseTest.sh
:
caseTest.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #!/bin/bash option="${1} " case ${option} in --start) echo "start" ;; --stop) echo "stop" ;; -r | --reload) DIR="${2} " echo "reload" ;; *) echo "Usage:${0} [-start] | [-stop] | [ -r|--reload]" exit 1 ;; esac
执行脚本:
1 2 3 4 5 6 7 8 $ ./caseTest.sh Usage:./caseTest.sh [--start] | [--stop] | [ -r|--reload] $ ./caseTest.sh --start start $ ./caseTest.sh --stop stop $ ./caseTest.sh -r reload
条件测试 测试命令 可以使用以下几种测试命令来判断条件是否满足:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 test EXPRESSION$ if test 1 -gt 2; then echo '1大于2' ; else echo '1小于2' ; fi 1小于2 [ EXPRESSION ] $ if [ 1 -gt 2 ]; then echo '1大于2' ; else echo '1小于2' ; fi 1小于2 [[ EXPRESSION ]] $ if [[ "123abc" =~ ^[a-z]+ ]]; then echo '字符串以英文字母开头' ; else echo '字符串不以英文字母开头' ; fi 字符串不以英文字母开头
高级特性 Bash shell 提供了两项在 if 语句中使用的高级命令。
双括号 相较于 test
命令只能在比较中使用简单的算术操作,双括号命令允许在比较过程中使用高级数学表达式:
表达式可以是数学赋值或比较表达式,除了 test
命令使用的标准数学运算符外,还支持一下运算符:
符号
描述
val++
后增
val--
后减
++val
先增
--val
先减
!
逻辑求反
~
按位求反
**
幂运算
<<
左移
>>
右移
&
按位和
|
按位或
&&
逻辑和
||
逻辑或
双方括号 整数测试 用于比较整数之间的数值大小,不支持比较浮点数:
[ INT1 -gt INT2 ]
:INT1
是否大于 INT2
[ INT1 -ge INT2 ]
:INT1
是否大于等于 INT2
[ INT1 -eq INT2 ]
:INT1
是否等于 INT2
[ INT1 -ne INT2 ]
:INT1
是否不等于 INT2
[ INT1 -lt INT2 ]
:INT1
是否小于 INT2
[ INT1 -le INT2 ]
:INT1
是否小于等于 INT2
示例:
1 2 3 4 5 6 7 8 $ if [ 1 -gt 2 ]; then echo '1大于2' ; else echo '1小于2' ; fi 1小于2 $ if [ 1 -lt 2 ]; then echo '1小于2' ; else echo '1大于2' ; fi 1小于2 $ if [ 1 -eq 2 ]; then echo '1等于2' ; else echo '1不等于2' ; fi 1不等于2 $ if [ 1 -ne 2 ]; then echo '1不等于2' ; else echo '1等于2' ; fi 1不等于2
字符串测试 用于测试单个字符串的长度是否为 0
以及是否为空,比较两个字符串之间的 ASCII 顺序,注意字符串与操作符之间要有空格 :
[ -z STR ]
:STR
长度是否为 0
[ -n STR ]
:STR
长度是否不为 0
[ STR ]
:STR
是否不为空,与 -n
类似
[ STR1 = STR2 ]
:两个字符串是否相同
[ STR1 == STR2 ]
:两个字符串是否相同
[ STR1 != STR2 ]
:两个字符串是否不相同
[[ STR1 < STR2 ]]
:STR1
是否排在 STR2
前面
[[ STR1 > STR2 ]]
:STR1
是否排在 STR2
后面
[[ STR =~ PARTTERN ]]
:STR
是否能够被 PARTTERN
模式匹配,支持扩展正则
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $ if [ -z "" ]; then echo '字符串长度为0' ; else echo '字符串长度不为0' ; fi 字符串长度为0 $ if [ -z $notDefine ]; then echo '字符串长度为0' ; else echo '字符串长度不为0' ; fi 字符串长度为0 $ if [ -n "" ]; then echo '字符串长度不为0' ; else echo '字符串长度为0' ; fi 字符串长度为0 $ if [ "" ]; then echo '字符串不为空' ; else echo '字符串为空' ; fi 字符串为空 $ if [ "a" == "b" ]; then echo '两个字符串相同' ; else echo '两个字符串不相同' ; fi 两个字符串不相同 $ if [ "a" != "b" ]; then echo '两个字符串不相同' ; else echo '两个字符串相同' ; fi 两个字符串不相同 $ if [ "abc" \> "aab" ]; then echo 'abc>aab' ; else echo 'abc<aab' ; fi abc>aab $ if [[ "abc" < "aab" ]]; then echo 'abc<aab' ; else echo 'abc>aab' ; fi abc>aab $ if [[ "123abc" =~ ^[a-z]+ ]]; then echo '字符串以英文字母开头' ; else echo '字符串不以英文字母开头' ; fi 字符串不以英文字母开头
逻辑测试 用于进行逻辑判断:
[ ! EXPR ]
:逻辑非;
[ EXPR1 -a EXPR2 ]
:逻辑与;
[ EXPR1 -o EXPR2 ]
:逻辑或;
[ EXPER1 ] && [ EXPER2 ]
:用逻辑与连接两个条件;
[ EXPER1 ] || [ EXPER2 ]
:用逻辑或连接两个条件;
[[ EXPER1 && EXPER2 ]]
:用逻辑与连接两个条件;
[[ EXPER1 || EXPER2 ]]
:用逻辑或连接两个条件。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ if [ ! 1 -eq 1 ]; then echo 'true' ; else echo 'false' ; fi false $ if [ 1 -eq 2 -a 2 -eq 2 ]; then echo 'true' ; else echo 'false' ; fi false $ if [ 1 -eq 2 -o 2 -eq 2 ]; then echo 'true' ; else echo 'false' ; fi true $ if [ 1 -eq 2 ] && [ 2 -eq 2 ]; then echo 'true' ; else echo 'false' ; fi false $ if [ 1 -eq 2 ] || [ 2 -eq 2 ]; then echo 'true' ; else echo 'false' ; fi true $ if [[ 1 -eq 2 && 2 -eq 2 ]]; then echo 'true' ; else echo 'false' ; fi false $ if [[ 1 -eq 2 || 2 -eq 2 ]]; then echo 'true' ; else echo 'false' ; fi true
文件测试 文件存在测试
[ -a FILE ]
:是否存在文件或目录;
[ -e FILE ]
:与 -a
相同。
文件类别测试
[ -b FILE ]
:是否存在块设备文件;
[ -c FILE ]
:是否存在字符设备文件;
[ -d FILE ]
:是否存在目录文件;
[ -f FILE ]
:是否存在普通文件;
[ -h FILE ]
:是否存在符号链接文件(在一些老系统上无效);
[ -p FILE ]
:是否存在命名管道文件;
[ -L FILE ]
:是否存在符号链接文件;
[ -S FILE ]
:是否存在套接字文件。
文件非空测试
文件权限测试 判断文件的 r/w/x
权限时依据的是当前登录用户,文件权限测试的前提拥有该文件所在目录的执行权限:
[ -r FILE ]
:是否存在且可读;
[ -w FILE ]
:是否存在且可写;
[ -x FILE ]
:是否存在且可执行;
[ -g FILE ]
:是否存在设置了 SGID 位的文件;
[ -k FILE ]
:是否存在设置了 sticky 位(冒险位)的文件;
[ -u FILE ]
:是否存在设置了 SUID 位的文件;
[ -O FILE ]
:当前有效用户是否为文件属主;
[ -G FILE ]
:当前有效用户是否为文件属组。
文件打开测试
[ -t FD ]
:文件描述符 FD(默认为 1
)是否打开并指向一个终端;
文件改动测试
[ -N FILE ]
:文件自从上一次被读取之后是否被修改过;
文件双目测试
[ FILE1 -ef FILE2 ]
:两个文件是否指向相同的设备和 *Inode号码 * ;
[ FILE1 -nt FILE2 ]
:FILE1
是否新于 FILE2
,或者是否 FILE1
存在但 FILE2
不存在;
[ FILE1 -ot FILE2 ]
:FILE1
是否旧于 FILE2
,或者是否 FILE1
不存在但 FILE2
存在。
循环 for循环 Shell 脚本支持 for
循环,有以下几种用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for 变量 in 值1 值2...; do command done for 变量 in 列表; do command done for ((初始值; 循环控制条件; 变量变化)); do command done
示例:
编写脚本 forTest.sh
:
forTest.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #!/bin/bash echo '开始循环多个值' for i in 1 2 3; do echo $i done echo '开始循环序列' for j in {4..6}; do echo $j done echo '开始循环seq序列' for k in $(seq 7 9); do echo $k done echo '开始循环ls列表' for lsList in $(ls /usr/ | grep s); do echo $lsList done echo '开始循环路径列表' for path in /usr/s*; do echo $path done echo '开始循环文件内容' for file in $(cat file); do echo $file done echo '开始循环控制条件' for ((l=10; l<=12; l++)); do echo $l done
运行脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 $ ./forTest.sh 开始循环多个值 1 2 3 开始循环序列 4 5 6 开始循环seq序列 7 8 9 开始循环ls列表 games sbin share src 开始循环路径列表 /usr/sbin /usr/share /usr/src 开始循环文件内容 line1 line2 line3 开始循环控制条件 10 11 12
while循环 只要条件成立,循环就会一直继续,直到条件不成立才会停止循环:
1 2 3 while [ EXPRESSION ]; do command done
示例:
编写脚本 whileTest.sh
:
whileTest.sh 1 2 3 4 5 6 7 8 #!/bin/bash i=1 sum=0 until [ $i -gt 100 ]; do sum=$(($sum +$i )) i=$(($i +1 )) done echo $sum
运行脚本:
until循环 只要条件不成立,循环就一直继续,一旦条件成立就终止循环:
1 2 3 until [ EXPRESSION ]; do command done
示例:
编写脚本 untilTest.sh
:
untilTest.sh 1 2 3 4 5 6 7 8 #!/bin/bash i=1 sum=0 while [ $i -gt 100 ]; do sum=$(($sum +$i )) i=$(($i +1 )) done echo $sum
运行脚本:
工具命令 echo 格式:
1 2 3 4 5 6 7 $ echo [SHORT-OPTION]... [STRING]... -n:不输出尾随换行符 -e:启用反斜杠转义的解释 -E:禁用反斜杠转义的解释(默认) $ /bin/echo --help
date date
:显示系统当前日期和时间
-s <STRING>
:根据字符串设置系统日期和时间;
-d <STRING>
:显示由字符串描述的日期和时间;
+<FORMAT>
:指定显示格式。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ date 2019年 05月 06日 星期一 20:00:00 CST $ date +"%Y-%m-%d %H:%M:%S" 2019-05-06 20:00:00 $ date +%F $ date +"%Y-%m-%d" 2019-05-06 $ date -d '-1 days' +%F 2019-05-05 $ date -d '-1 months' +%Y%m 201904 $ date -d "+18848 day 19700101" +%F 2021-08-09
lsof lsof(list open files)是一个列出当前系统打开文件的工具。在 Linux 中任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件。由于应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此使用 lsof 工具查看这个列表对系统监测以及排错将是很有帮助的。格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 lsof [选项] [文件] 选项: -a:列出打开文件的进程 -c <进程名>:列出指定进程打开的文件 -g <GID>:列出 GID 进程详情 -d <F>:列出占用该文件的进程 +d <dir>:列出目录下打开的文件 +D <dir>:递归列出目录下打开的文件 -i <[4|6][proto][@host|addr][:svc_list|port_list]>:列出符合条件的进程,比如 IP 类型(IPv4/6)、协议、IP 地址、端口号 -p <PID>:列出指定进程号所打开的文件 -u <UID>
示例 查看文件是否被占用:
1 2 3 4 5 6 7 8 #!/bin/bash flag=$( /usr/sbin/lsof /etc/file.txt | wc -l ) if [ $flag == "0" ];then echo "文件没被占用" else echo "文件被占用" exit fi
其他常见用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $ lsof /bin/bash $ lsof -a /bin/bash COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME bash 35918 hadoop txt REG 253,2 1089480 291512 /usr/bin/bash bash 67647 hadoop txt REG 253,2 1089480 291512 /usr/bin/bash bash 67731 hadoop txt REG 253,2 1089480 291512 /usr/bin/bash startHive 68761 hadoop txt REG 253,2 1089480 291512 /usr/bin/bash $ lsof -c startHive COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME startHive 68761 hadoop cwd DIR 253,3 278 16777286 /home/hadoop startHive 68761 hadoop rtd DIR 253,0 273 64 / startHive 68761 hadoop txt REG 253,2 1089480 291512 /usr/bin/bash startHive 68761 hadoop mem REG 253,2 106075056 50349361 /usr/lib/locale/locale-archive startHive 68761 hadoop mem REG 253,2 2151672 16794937 /usr/lib64/libc-2.17.so startHive 68761 hadoop mem REG 253,2 19288 16794943 /usr/lib64/libdl-2.17.so startHive 68761 hadoop mem REG 253,2 163400 16794930 /usr/lib64/ld-2.17.so startHive 68761 hadoop mem REG 253,2 26254 16795266 /usr/lib64/gconv/gconv-modules.cache startHive 68761 hadoop 0u CHR 136,0 0t0 3 /dev/pts/0 startHive 68761 hadoop 1u CHR 136,0 0t0 3 /dev/pts/0 startHive 68761 hadoop 2u CHR 136,0 0t0 3 /dev/pts/0 startHive 68761 hadoop 255r REG 8,65 162 2147559258 /app/data4/shellScript/startHive.sh
sleep sleep NUMBER[SUFFIX]...
:暂停数秒时间,SUFFIX
可以是以下几种:
示例:
编写脚本 loop.sh
,输出数字 1~5,每秒一次:
loop.sh 1 2 3 4 5 #!/bin/bash for ((i = 1; i <= 5; i++)); do echo $i sleep 1 done
编写脚本 sleep.sh
,调用并在后台执行 loop.sh
脚本,暂停 3 秒后执行 echo
:
sleep.sh 1 2 3 4 #!/bin/bash /root/shellScript/loop.sh & sleep 3 echo 'loop.sh Finished'
执行 sleep.sh
,可以看到在 loop.sh
执行开始 3 秒后,执行了 echo
:
1 2 3 4 5 6 7 $ ./sleep.sh 1 2 3 loop.sh Finished 4 5
如果 loop.sh
不是在后台执行,可以看到会在 loop.sh
执行完后会等待 3 秒,再输出 echo
信息。
wait wait [-n] [ID...]
:wait
命令用于等待任务完成并返回任务的退出状态
示例:
编写脚本 wait.sh
,调用并在后台执行 loop.sh
脚本:
wait.sh 1 2 3 4 5 6 7 8 #!/bin/bash /root/shellScript/loop.sh & wait echo 'loop.sh Finished' /root/shellScript/loop.sh & pid=$! wait $pid echo "loop.sh $pid Finished"
执行 wait.sh
:
1 2 3 4 5 6 7 8 9 10 11 12 13 $ ./wait.sh 1 2 3 4 5 loop.sh Finished 1 2 3 4 5 loop.sh 11200 Finished
basename basename
命令用于从路径字符串中提取文件名,可以选择是否保留文件后缀:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 basename NAME [SUFFIX] 选项: -a, --multiple 支持处理多个文件名 -s, --suffix=<SUFFIX> 指定文件后缀为 SUFFIX 并删除后缀 -z, --zero 输出多个文件名时不使用换行分割 $ basename /usr/bin/sannaha.log sannaha.log $ basename bin/startHive.sh .sh startHive $ basename -a bin/startHive.sh bin/startHBase.sh startHive.sh startHBase.sh $ basename -a -s .sh bin/startHive.sh bin/startHBase.sh startHive startHBase $ basename -a -z -s .sh bin/startHive.sh bin/startHBase.sh startHivestartHBase scriptName=$(basename $0 )
dirname dirname
命令用于从路径字符串中提取目录,会删除字符串中最后一个 /
及后面的内容,如果名称不包含 /
,则输出 .
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 dirname [OPTION] NAME... 选项: -z, --zero 输出多个目录时不使用换行分割 $ dirname /usr/bin/sannaha.log /usr/bin $ dirname bin/startHive.sh startHBase.sh bin . $ dirname -z bin/startHive.sh startHBase.sh bin. scriptDir=$(dirname $0 )
awk awk 是一个处理文本文件的应用程序,几乎所有 Linux 系统都自带这个程序。它依次处理文件的每一行,并读取里面的每一个字段。对于日志、CSV 那样的每行格式相同的文本文件,awk 可能是最方便的工具。
基本格式 awk 的基本格式:
1 2 3 4 5 $ awk 动作 文件名 $ awk '{print $0}' demo.txt
上面示例中,demo.txt
是 awk
所要处理的文本文件。前面单引号内部有一个大括号,里面就是每一行的处理动作 print $0
。其中 print
是打印命令,$0
代表当前行,因此上面命令的执行结果,就是把每一行原样打印出来。
用标准输入(stdin)演示这个例子:
1 2 3 $ echo 'this is a test' | awk '{print $0}' this is a test
awk
会根据空格和制表符,将每一行分成若干字段,依次用 $1
、$2
、$3
… 代表第一个字段、第二个字段、第三个字段等:
1 2 3 $ echo 'this is a test' | awk '{print $3}' a
为了便于接下来的演示,把 /etc/passwd
文件保存成 passwd.txt
:
1 2 3 4 5 6 $ cat /etc/passwd > passwd.txt root:x:0:0:root:/root:/bin/bash daemon:x:2:2:daemon:/sbin:/sbin/nologin bin:x:1:1:bin:/bin:/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync
这个文件的字段分隔符是冒号(:
),所以要用-F
参数指定分隔符为冒号。然后,才能提取到它的第一个字段。
1 2 3 4 5 6 $ awk -F ':' '{ print $1 }' demo.txt root daemon bin sys sync
变量 除了 $数字
表示某个字段,awk
还提供其他一些其他内置变量:
NF
: 表示当前行有多少个字段,因此 $NF
代表最后一个字段,$(NF-1)
就代表倒数第二个字段
NR
:表示当前处理的是第几行
FILENAME
:当前文件名
FS
:字段分隔符,默认是空格和制表符。
RS
:行分隔符,用于分割每一行,默认是换行符。
OFS
:输出字段的分隔符,用于打印时分隔字段,默认为空格。
ORS
:输出记录的分隔符,用于打印时分隔记录,默认为换行符。
OFMT
:数字输出的格式,默认为%.6g
。
1 2 3 4 5 $ echo 'this is a test' | awk '{print $NF}' test $ echo 'this is a test' | awk '{print $(NF-1), $NF}' a test
上面代码中,print
命令里面的逗号,表示输出的时候,两个部分之间使用空格分隔。
变量 NR
表示当前处理的是第几行。
1 2 3 4 5 6 $ awk -F ':' '{print NR ") " $1}' demo.txt 1) root 2) daemon 3) bin 4) sys 5) sync
上面代码中,print
命令里面,如果原样输出字符,要放在双引号里面。
awk
的其他内置变量如下:
FILENAME
:当前文件名
FS
:字段分隔符,默认是空格和制表符。
RS
:行分隔符,用于分割每一行,默认是换行符。
OFS
:输出字段的分隔符,用于打印时分隔字段,默认为空格。
ORS
:输出记录的分隔符,用于打印时分隔记录,默认为换行符。
OFMT
:数字输出的格式,默认为%.6g
。
函数 awk
还提供了一些内置函数,方便对原始数据的处理。
函数toupper()
用于将字符转为大写。
1 2 3 4 5 6 $ awk -F ':' '{ print toupper($1) }' demo.txt ROOT DAEMON BIN SYS SYNC
上面代码中,第一个字段输出时都变成了大写。
其他常用函数如下。
tolower()
:字符转为小写。
length()
:返回字符串长度。
substr()
:返回子字符串。
sin()
:正弦。
cos()
:余弦。
sqrt()
:平方根。
rand()
:随机数。
awk
内置函数的完整列表,可以查看 手册
条件 awk
允许指定输出条件,只输出符合条件的行。
输出条件要写在动作的前面。
请看下面的例子。
1 2 3 4 5 $ awk -F ':' '/usr/ {print $1}' demo.txt root daemon bin sys
上面代码中,print
命令前面是一个正则表达式,只输出包含usr
的行。
下面的例子只输出奇数行,以及输出第三行以后的行。
1 2 3 4 5 6 7 8 9 10 $ awk -F ':' 'NR % 2 == 1 {print $1}' demo.txt root bin sync $ awk -F ':' 'NR >3 {print $1}' demo.txt sys sync
下面的例子输出第一个字段等于指定值的行。
1 2 3 4 5 6 $ awk -F ':' '$1 == "root" {print $1}' demo.txt root $ awk -F ':' '$1 == "root" || $1 == "bin" {print $1}' demo.txt root bin
if语句 awk
提供了if
结构,用于编写复杂的条件。
1 2 3 4 $ awk -F ':' '{if ($1 > "m") print $1}' demo.txt root sys sync
上面代码输出第一个字段的第一个字符大于m
的行。
if
结构还可以指定else
部分。
1 2 3 4 5 6 $ awk -F ':' '{if ($1 > "m") print $1; else print "---"}' demo.txt root --- --- sys sync
示例 1 2 3 4 5 6 7 8 9 awk -v OFS=',' -F ',' '{print $1,$2}' file.csv > file_awk.csv awk -v OFS="," -F ',' '{if ($1 == "sannaha") print $1,$2,$NF}' "${TMP_PATH} " >>"${PUSH_PATH} "
sed sed 命令主要用来处理和编辑文件,简化对文件的反复操作,能够完美的配合正则表达式使用。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。
语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 sed [-hnV][-e<script>][-f<script文件>][文本文件] 参数说明: -e<script>或--expression=<script> 以选项中指定的script来处理输入的文本文件。 -f<script文件>或--file=<script文件> 以选项中指定的script文件来处理输入的文本文件。 -h或--help 显示帮助。 -n或--quiet或--silent 仅显示script处理后的结果。 -V或--version 显示版本信息。 -i直接编辑文件 动作说明: a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~ c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行! d :删除,因为是删除啊,所以 d 后面通常不接任何咚咚; i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行); p :打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~ s :取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 就是啦! ------ sed命令: a\ 在当前行下面插入文本。 i\ 在当前行上面插入文本。 c\ 把选定的行改为新的文本。 d 删除,删除选择的行。 D 删除模板块的第一行。 s 替换指定字符 h 拷贝模板块的内容到内存中的缓冲区。 H 追加模板块的内容到内存中的缓冲区。 g 获得内存缓冲区的内容,并替代当前模板块中的文本。 G 获得内存缓冲区的内容,并追加到当前模板块文本的后面。 l 列表不能打印字符的清单。 n 读取下一个输入行,用下一个命令处理新的行而不是用第一个命令。 N 追加下一个输入行到模板块后面并在二者间嵌入一个新行,改变当前行号码。 p 打印模板块的行。 P(大写) 打印模板块的第一行。 q 退出Sed。 b lable 分支到脚本中带有标记的地方,如果分支不存在则分支到脚本的末尾。 r file 从file中读行。 t label if 分支,从最后一行开始,条件一旦满足或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾。 T label 错误分支,从最后一行开始,一旦发生错误或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾。 w file 写并追加模板块到file末尾。 W file 写并追加模板块的第一行到file末尾。 ! 表示后面的命令对所有没有被选定的行发生作用。 = 打印当前行号码。 替换标记: g 表示行内全面替换。 p 表示打印行。 w 表示把行写入一个文件。 x 表示互换模板块中的文本和缓冲区中的文本。 y 表示把一个字符翻译为另外的字符(但是不用于正则表达式) \1 子串匹配标记 & 已匹配字符串标记 元字符集 ^ 匹配行开始,如:/^sed/匹配所有以sed开头的行。 $ 匹配行结束,如:/sed$/匹配所有以sed结尾的行。 . 匹配一个非换行符的任意字符,如:/s.d/匹配s后接一个任意字符,最后是d。 * 匹配0个或多个字符,如:/*sed/匹配所有模板是一个或多个空格后紧跟sed的行。 [] 匹配一个指定范围内的字符,如/[ss]ed/匹配sed和Sed。 [^] 匹配一个不在指定范围内的字符,如:/[^A-RT-Z]ed/匹配不包含A-R和T-Z的一个字母开头,紧跟ed的行。 \(..\) 匹配子串,保存匹配的字符,如s/\(love\)able/\1rs,loveable被替换成lovers。 & 保存搜索字符用来替换其他字符,如s/love/**&**/,love这成**love**。 \< 匹配单词的开始,如:/\<love/匹配包含以love开头的单词的行。 \> 匹配单词的结束,如/love\>/匹配包含以love结尾的单词的行。 x\{m\} 重复字符x,m次,如:/0\{5\}/匹配包含5个0的行。 x\{m,\} 重复字符x,至少m次,如:/0\{5,\}/匹配至少有5个0的行。 x\{m,n\} 重复字符x,至少m次,不多于n次,如:/0\{5,10\}/匹配5~10个0的行。
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 $ echo aa,bb,cc, | sed 's/,$//' >> result.txt $ cat result.txt aa,bb,cc sed -i 's/defaultQueue/sannahaQueue/g' ./*.sh sed -i 's/\/app\/data4/\/app\/data1/g' table_backup.sh insert overwrite local directory '/local/dir' row format delimited fields terminated by ',' select col_name1,col_name2 from table_name ; $ ls /local /dir/ 000000_0 000001_0 ... $ cat *_0 > single.csv $ sed -i '1i col_name1,col_name2' single.csv $ head single.csv col_name1,col_name2 data1,data2
wc wc
命令用于计算文件的行数、字数和字节数,如果文件个数不止一个,还会将多个文件汇总统计。统计行数实际上是统计换行符 \n
出现的次数,统计字数则是统计用空格分隔的非空字符序列的个数。
语法 1 2 3 4 5 6 7 wc [option] <file...> 参数: -l, --lines 显示行数 -w, --words 显示字数 -m, --chars 显示字符数 -c, --bytes 显示字节数 -L, --max-line-length 显示最长行的长度
示例 1 2 3 4 5 6 7 8 9 10 11 12 13 $ wc tmp.txt 3 5 49 tmp.txt $ wc -lwmcL tmp.txt 3 5 49 49 32 tmp.txt $ wc file1.txt file2.csv 3105520 3105520 37266240 file1.txt 3195780 3207266 162007753 file2.csv 6301300 6312786 199273993 total
ftp 语法 1 2 3 4 5 6 7 ftp [option] [host] 参数: -v:强制FTP显示来自远程服务器的所有响应,以及数据传输统计信息的报告。 -d:启用调试,显示FTP客户端和FTP服务器之间传递的所有命令。 -i:在多个文件传输期间禁用交互式提示。 -n:在初始连接时禁止自动登录。 -g:禁止使用通配符。Glob 允许使用星号 (*) 和问号 (?) 作为本地文件名和路径名中的通配符。
连接服务器 1 2 $ ftp -in 192.168.153.121 ftp> user ftpadmin ftpadmin
上传文件 1 2 3 4 5 6 7 ftp> put result.txt ftp> put result.txt dir/res.txt ftp> mput filename*
下载文件 1 2 3 4 5 6 7 8 9 10 11 12 ftp> get rfile ftp> get rfile lfile ftp> mget *.txt mget result.txt? y 6 bytes received in 0.000301 secs (19.93 Kbytes/sec) mget result2.txt? y 4 bytes received in 0.000582 secs (6.87 Kbytes/sec)
更改传输模式 1 2 3 4 5 6 7 8 9 10 11 ftp> type Using binary mode to transfer files. ftp> asc 200 Switching to ASCII mode. ftp> bin 200 Switching to Binary mode.
示例 获取文件列表
使用 nlist
命令获取文件列表:
1 2 3 4 5 6 7 8 9 #!/bin/bash ftp_dir='/ftp_dir' ls_file="/app/data/sannaha/ls.txt" ftp -in 192.168.153.121 << EOT user username password nlist ${ftp_dir} ${ls_file} bye EOT
查看文件内容:
1 2 3 4 $ cat /app/data/sannaha/ls.txt /ftp_dir/model_test1.csv /ftp_dir/model_test2.csv /ftp_dir/test.csv
使用 ls
命令获取文件列表,支持通配符,显示文件大小等信息:
1 2 3 4 5 6 7 8 9 10 #!/bin/bash ftp_dir="/ftp_dir" ls_file="/app/data/sannaha/ls.txt" ftp -in 192.168.153.121 << EOT > ${ls_file} user username password cd ${ftp_dir} ls model* bye EOT
查看文件内容:
1 2 3 $ cat /app/data/sannaha/ls.txt -rw-r--r-- 1 1019 1019 92985063 Aug 07 02:17 model_test1.csv -rw-r--r-- 1 1019 1019 84522406 Sep 09 10:42 model_test2.csv
判断文件是否存在 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #!/bin/bash ftp_dir='/ftp_dir' ftp_filename="filename.txt" local_dir="/local_dir" file_num=$(echo " user username password cd ${ftp_dir} ls bye " | ftp -v -n 192.168.153.121 | grep "${ftp_filename} " | wc -l)if [ $file_num -ne 1 ]; then echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : 文件不存在, file_num=${file_num} exit 1 fi echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : 文件存在
判断文件大小 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #!/bin/bash ftp_dir='/ftp_dir' ftp_filename="filename.txt" local_dir="/local_dir" file_size=$(echo " user username password cd ${ftp_dir} ls bye " | ftp -v -n 192.168.153.121 | grep ${ftp_filename} | awk '{print $5}' )if [ $file_size -lt 100000000 ]; then echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : 文件大小异常, file_size=${file_size} exit 1 fi echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : 文件大小正常
下载文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #!/bin/bash ftp_dir='/ftp_dir' ftp_filename="filename.txt" local_dir="/local_dir" echo " user username password prompt lcd ${local_dir} cd ${ftp_dir} !pwd pwd binary get ${ftp_filename} bye " | ftp -v -n 192.168.153.121if [ $? -ne 0 ]; then echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : 文件下载失败 exit 1 fi echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : 文件下载完成
lftp lftp 是一个功能强大的文件传输工具,支持 ftp 协议。
语法 1 2 3 lftp [-d] [-e cmd] [-p port] [-u user[,pass]] [site] lftp -f script_file lftp -c commands
连接服务器 1 2 3 4 5 6 7 8 9 10 11 12 13 $ lftp ftp://192.168.153.121 lftp 192.168.153.121:~> user ftpadmin ftpadmin $ lftp ftp://ftpadmin:ftpadmin@192.168.153.121 $ lftp -p 21 -u ftpadmin,ftpadmin ftp://192.168.153.121 $ lftp -p 21 -u ftpadmin,ftpadmin ftp://192.168.153.121 -e "cd data; put top.sql -o top.sql.tmp;mv top.sql.tmp top.sql;exit;" >> /dev/null lftp ftpadmin@192.168.153.121:~> exit
操作服务器 lftp 中 pwd
/ ls
/ cd
/ mv
/ rm
/ mkdir
/ cat
等命令的功能与 Shell 基本一致,用来查看与操作 FTP 服务器上的文件和目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 lftp ftpadmin@192.168.153.121:~> pwd ftp://ftpadmin@192.168.153.121:21 lftp ftpadmin@192.168.153.121:~> pwd -p ftp://ftpadmin:ftpadmin@192.168.153.121:21 lftp ftpadmin@192.168.153.121:~> ls drwx------ 2 ftp ftp 71 Jul 21 08:59 data -rw------- 1 ftp ftp 0 Jul 21 08:58 result.txt lftp ftpadmin@192.168.153.121:/> cls data/ lftp ftpadmin@192.168.153.121:/> cd data/
操作本地 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 lftp ftpadmin@192.168.153.121:~> lpwd /opt/bigdata/hivedata lftp ftpadmin@192.168.153.121:~> lcd /opt/bigdata/ lcd 成功, 本地目录=/opt/bigdata lftp ftpadmin@192.168.153.121:~> !ls course.csv score.csv student.csv teacher.csv lftp ftpadmin@192.168.153.121:/data> !pwd /root lftp ftpadmin@192.168.153.121:/data> lpwd /root lftp ftpadmin@192.168.153.121:/data> ! cd /opt/ lftp ftpadmin@192.168.153.121:/data> lpwd /root lftp ftpadmin@192.168.153.121:/data> lcd /opt lcd 成功, 本地目录=/opt lftp ftpadmin@192.168.153.121:/data> lpwd /opt
下载文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 > get hadoopserver.tar -o hadoopserver.tar.tmp 中断 > mget *.csv 522 bytes transferred Total 2 files transferred > pget -c -n 10 hadoopserver.tar -o hadoopserver.tar.tmp 814088563 bytes transferred in 18 seconds (42.84M/s)
上传文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 > put result.txt -o result.txt.tmp > mput *.csv 654 bytes transferred Total 4 files transferred
移动和删除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 lftp ftpadmin@192.168.153.121:/data> mv result.txt result.txt.tmp rename successful > rm dir rm: Access failed: 550 Delete operation failed. (dir) > rm -f dir > rm -r dir rm 成功, 删除 `dir' # 删除文件,支持通配符 # mrm file(s) > mrm file*.txt rm ok, 3 files removed
镜像目录 1 2 3 4 5 6 7 8 > mirror -R hivedata Total: 1 directory, 6 files, 0 symlinks New: 6 files, 0 symlinks 654 bytes transferred
示例 获取文件列表 1 2 3 4 5 #!/bin/bash ftp_dir="/ftp_dir" ls_file="/app/data/sannaha/ls.txt" lftp ftp://username:password@192.168.153.121 -e "cd ${ftp_dir} ; ls; exit;" > ${ls_file}
判断文件是否存在 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #!/bin/bash ftp_dir="/ftp_dir" ftp_filename="filename.txt" ftp_filename2="filename2.txt" ls_log="/app/data/sannaha/ls.log" lftp ftp://username:password@192.168.153.121 -e "cd ${ftp_dir} ; ls; exit;" > ${ls_log} file_num=$(grep ${ftp_filename} ${ls_log} | wc -l) file_num2=$(grep ${ftp_filename2} ${ls_log} | wc -l) if [ $file_num -ne 1 ]; then echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : ${ftp_filename} 不存在, file_num=${file_num} exit 1 fi if [ $file_num2 -ne 1 ]; then echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : ${ftp_filename2} 不存在, file_num=${file_num} exit 1 fi echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : 文件存在
判断文件大小 1 2 3 4 5 6 7 8 9 10 11 12 13 #!/bin/bash ftp_dir='/ftp_dir' ftp_filename="filename.txt" local_dir="/local_dir" file_size=$(lftp -p 21 -u username,password ftp://192.168.153.121 -e "ls ${ftp_dir} ; exit;" | grep "${ftp_filename} " | awk '{print $5}' ) if [ $file_size -lt 100000000 ]; then echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : 文件大小异常, file_size=${file_size} exit 1 fi echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : 文件大小正常
下载文件 1 2 3 4 5 6 #!/bin/bash ftp_dir='/ftp_dir' ftp_filename="filename.txt" local_dir="/local_dir" lftp ftp://username:password@192.168.153.121 -e "lcd ${local_dir} ; cd ${ftp_dir} ; get ${ftp_filename} ; exit;"
except expect 是一个用来处理交互的命令,可以使用 expect 完成登录时密码的自动输入。
语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 spawn [args] program [args] send [-flags] string expect [[-opts] pat1 body1] ... [-opts] patn [bodyn] expect { busy {puts busy\n ; exp_continue} failed abort "invalid password" abort timeout abort connected } interact [string1 body1] ... [stringn [bodyn]]
示例 编写 expect
脚本:
expect_lftp_cmd.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #!/usr/bin/expect if {$argc != 4} { send_user "Usage:$argv0 <ip> <username> <password> <lftp COMMEND>\n" exit 1 } set timeout 30set ip [lindex $argv 0]set url "ftp://$ip " set username [lindex $argv 1]set password [lindex $argv 2]set COMMEND [lindex $argv 3]spawn lftp -u ${username} ${url} expect "*口令:*" {send "$password \r" } send "$COMMEND \r" send "exit\r" expect eof
执行命令:
1 $ ./expect_lftp_demo.sh 192.168.153.121 ftpadmin ftpadmin "mput /opt/bigdata/script/*.sh"
$argc
表示参数个数;
$argv0
表示可执行文件的名字,此处即为脚本名 ./expect_lftp_cmd.sh
;
[lindex $argv 0]
表示第一个参数,[lindex $argv 1]
表示第二个参数…;
set
用来设置变量;
set timeout num
用来设置超时时间(秒),默认为 10
,无限超时可以设置为 -1
;
spawn
用于启动一个新进程;
expect
用来等待匹配以下几种模式:从衍生进程接收到特定字符串;超过指定的时间;看到文件结尾。根据进程的反馈,再发送对应的交互命令。如果模式的关键字是 timeout
,则在超时后执行相应的命令,如果关键字是 default
,则在超时或文件结束时执行相应的命令。
send
向当前进程发送一个字符串,换行符用 \r
表示;
interact
将当前进程的控制权交给用户,即允许用户与进程交互。
范例参考 参考资料:*expect - 自动交互脚本 * ,*expect教程中文版 * ,*expect说明 * 。
自动 telnet 会话:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/usr/bin/expect -f set ip [lindex $argv 0 ] set userid [lindex $argv 1 ] set mypassword [lindex $argv 2 ] set mycommand [lindex $argv 3 ] set timeout 10 spawn telnet $ip expect "username:" send "$userid \r" expect "password:" send "$mypassword \r" expect "%" send "$mycommand \r" expect "%" set results $expect_out (buffer) send "exit\r" expect eof
自动建立 FTP 会话:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #!/usr/bin/expect -f set ip [lindex $argv 0 ] set userid [lindex $argv 1 ] set mypassword [lindex $argv 2 ] set timeout 10 spawn ftp $ip expect "username:" send "$userid \r" expect "password:" send "$mypassword \r" expect "ftp>" send "bin\r" expect "ftp>" send "prompt\r" expect "ftp>" send "mget *\r" expect "ftp>" send "bye\r" expect eof
自动登录 ssh 执行命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #!/usr/bin/expect set IP [lindex $argv 0]set USER [lindex $argv 1]set PASSWD [lindex $argv 2]set CMD [lindex $argv 3] spawn ssh $USER @$IP $CMD expect { "(yes/no)?" { send "yes\r" expect "password:" send "$PASSWD \r" } "password:" {send "$PASSWD \r" } "* to host" {exit 1} } expect eof
自动登录 ssh:
1 2 3 4 5 6 7 8 9 10 11 12 #!/usr/bin/expect -f set ip [lindex $argv 0 ] set username [lindex $argv 1 ] set mypassword [lindex $argv 2 ] set timeout 10 spawn ssh $username @$ip expect { "*yes/no" { send "yes\r" ; exp_continue} "*password:" { send "$mypassword \r" } } interact
批量登录 ssh 服务器执行操作范例,设定增量的 for 循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #!/usr/bin/expect for {set i 10} {$i <= 12} {incr i} { set timeout 30 set ssh_user [lindex $argv 0] spawn ssh -i .ssh/$ssh_user abc$i .com expect_before "no)?" { send "yes\r" } sleep 1 expect "password*" send "hello\r" expect "*#" send "echo hello expect! > /tmp/expect.txt\r" expect "*#" send "echo\r" } exit
批量登录 ssh 并执行命令,foreach 语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #!/usr/bin/expect if {$argc !=2} { send_user "usage: ./expect ssh_user password\n" exit } foreach i {11 12} { set timeout 30 set ssh_user [lindex $argv 0] set password [lindex $argv 1] spawn ssh -i .ssh/$ssh_user root@xxx.yy.com expect_before "no)?" { send "yes\r" } sleep 1 expect "Enter passphrase for key*" send "password\r" expect "*#" send "echo hello expect! > /tmp/expect.txt\r" expect "*#" send "echo\r" } exit
另一自动 ssh 范例,从命令行获取服务器 IP,foreach 语法,expect 嵌套:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #!/usr/bin/expect set timeout 20if {$argc < 1} { puts "Usage: script IPs" exit 1 } set user "username" set password "yourpassword" foreach IP $argv { spawn ssh $user @$IP expect \ "(yes/no)?" { send "yes\r" expect "password:?" { send "$password \r" } } "password:?" { send "$password \r" } expect "\$?" send "last\r" expect "\$?" sleep 10 send "exit\r" expect eof }
ssh 自动登录 expect 脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #!/usr/bin/expect -f if {$argc < 4} { send_user "Usage:\n $argv0 IPaddr User Passwd Port Passphrase\n" puts stderr "argv error!\n" sleep 1 exit 1 } set ip [lindex $argv 0 ]set user [lindex $argv 1 ]set passwd [lindex $argv 2 ]set port [lindex $argv 3 ]set passphrase [lindex $argv 4 ]set timeout 6if {$port == "" } { set port 22 } spawn ssh -p $port $user @$ip expect_before "(yes/no)\\?" { send "yes\r" } expect \ "Enter passphrase for key*" { send "$passphrase \r" exp_continue } " password:?" { send "$passwd \r" exp_continue } "*\[#\\\$]" { interact } "* to host" { send_user "Connect faild!" exit 2 } timeout { send_user "Connect timeout!" exit 2 } eof { send_user "Lost connect!" exit }
实例 产生随机数 通过系统环境变量 $RANDOM
产生随机数:
1 2 3 4 5 6 7 8 9 10 11 $ echo $RANDOM 114 $ echo $RANDOM 514 echo $RANDOM |md5sum |cut -c 1-86ccfflty echo $RANDOM |cksum |cut -c 1-819198100
判断Hive表是否存在 1 2 3 4 5 6 7 8 9 #!/bin/bash month=$(date +%Y%m) table_exist=$(hive -e "desc sannaha.tbname_${month} ;" 2>&1 | grep "Table not found" ) if [ -z "$table_exist " ]; then echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : "db.tbname_${month} 已具备" else echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : "db.tbname_${month} 不具备" exit 1 fi
判断Hive表是否有数据 1 2 3 4 5 6 7 8 9 #!/bin/bash month=$(date +%Y%m) data_exist=$(hdfs dfs -ls hdfs://Cluster1/user/hive/warehouse/sannaha.db/tbname/dt=${month} ) if [[ $(echo ${data_exist} | grep "000000_0" ) != "" ]]; then echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : "sannaha.tbname${month} 分区数据已具备" else echo -e $(date "+%Y-%m-%d %H:%M:%S" ) [SHELL_LOG] : "sannaha.tbname${month} 分区数据不具备" exit 1 fi
SparkSQL跑批 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 . ~/.bashrc d_date=${1} if [ "X${d_date} " == "X" ]; then d_date=`date -d "-1 day" +"%Y%m%d" ` fi m_date=`date -d "${d_date} " +"%Y%m" ` df_date=`date -d "${d_date} " +"%Y%m01" ` d_p01d_date=`date -d "${d_date} -1 days" +%"Y%m%d" ` m_p01m_date=`date -d "${df_date} -1 month" +%"Y%m" ` d_n01d_date=`date -d "${d_date} 1 days" +%"Y%m%d" ` echo "日期:" $d_date echo "月份:" $m_date echo "当月月初日期:" $df_date echo "前1天日期:" $d_p01d_date echo "前1月月份:" $m_p01m_date echo "后1天日期:" $d_n01d_date kerberos_conf=" --principal user@SANNAHA --keytab /home/sannaha/user.keytab " conf="--conf spark.files.ignoreMissingFiles=true --conf spark.sql.files.ignoreMissingFiles=true --conf spark.files.ignoreCorruptFiles=true --conf spark.sql.files.ignoreCorruptFiles=true --conf spark.sql.shuffle.partitions=200 --conf spark.default.parallelism=600 --conf spark.driver.maxResultSize=10g --conf spark.yarn.executor.memoryOverhead=1024 --conf spark.executor.extraJavaOptions=-Xss4m --conf spark.driver.extraJavaOptions=-Xss4m" task_name="dw_train_ds_${d_date} " driver_memory=8g num_executors=200 executor_memory=3g executor_cores=1 master=yarn shell="/usr/bch/1.5.0/spark/bin/spark-shell \ --conf spark.ui.filters= \ --deploy-mode client \ --conf spark.yarn.stagingDir=hdfs://ns/user \ --conf spark.hadoop.dfs.replication=2 \ --queue root.bdoc.qwrk \ --name ${task_name} \ --master yarn \ --executor-memory ${executor_memory} \ --executor-cores ${executor_cores} \ --num-executors ${num_executors} \ --driver-memory ${driver_memory} \ --jars /home/sannaha/libs/postgresql-42.2.14.jar \ ${conf} \${kerberos_conf} "echo $shell echo "*************************** Complete time : `date` ***************************" eval $shell <<!EOF import org.apache.spark.sql._ import org.apache.spark.sql.types._ import org.apache.spark.sql.SaveMode spark.sqlContext.setConf("spark.sql.shuffle.partitions" ,"1200" ) val d_date="${d_date} " val m_date="${m_date} " //基站级网络用户 spark.read.parquet("hdfs://ns/user/people/ods/ods_everybase/day=" +d_date).registerTempTable("temp_ods_everybase" ) //特殊区域 spark.read.parquet("hdfs://ns/user/people/dim/dim_channel_cell_level_6" ).filter("channel_subtype = '火车站'" ).registerTempTable("dim_channel_cell_level_6" ) //当天在火车站出现过的人的记录 sql("" "select a.imsi,b.channel_id,a.enter_time,a.leave_time,a.sec from temp_ods_everybase a inner join dim_channel_cell_level_6 b on a.lac_id = b.lac_id and a.cell_id = b.cell_id " "" ).registerTempTable("temp_ods_everybase_train" ) //得出时间排序下的 每个用户的 在所有车站记录序号和在每个车站的记录序号 再相减 sql("" " select imsi,channel_id,enter_time,leave_time,sec ,row_number() over(partition by imsi order by enter_time)-row_number() over(partition by imsi,channel_id order by enter_time) group_flag from temp_ods_everybase_train " "" ).registerTempTable("temp_train_group" ) //聚合-链表a-b-c-a会有4条记录 sql("" " select imsi,channel_id,min(enter_time) enter_time,max(leave_time) leave_time,sum(sec) sec_sum from temp_train_group group by imsi,channel_id,group_flag " "" ).write.mode(SaveMode.Overwrite).parquet("hdfs://ns/user/people/dw/dw_nationwide_train_ds/month=" +sm_date+"/day=" +sd_date) spark.stop() !EOF echo "*************************** Complete time : `date` ***************************" exit 0
存储过程跑批 data_load.sh
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 if [ $# != 3 ]; then echo "参数输入有误,请重新输入!" echo "参数1:当前加载表的物理表名(ODS库中的物理表名)" echo "参数2:当期跑批日期,格式:YYYYMMDD" echo "参数3:完整的OK文件名称:***.OK" exit -1 fi tabnm=$1 dataDate=$2 fileok=$3 dbuser=ods dbpwd=ods dbip=172.xx.xx.xx dbport=1521 dbsid=rpmdb logPath="/u01/db/oradata/RPM_LOAD/RPM_SHELL/log/${dataDate} " dataBad="/u01/db/oradata/RPM_LOAD/RPM_DATA/BAD_DIR/${dataDate} " dataLog="/u01/db/oradata/RPM_LOAD/RPM_DATA/LOG_DIR/${dataDate} " dataDat="/u01/db/oradata/RPM_LOAD/RPM_DATA/DAT_DIR/${dataDate} " echo "${tabnm} 表数据开始加载:`date`" sqlplus ${dbuser} /${dbpwd} @${dbip} :${dbport} /${dbsid} >> ${logPath} /exec_proc.log <<EOF SET SERVEROUTPUT ON; SET VERIFY OFF; WHENEVER SQLERROR EXIT FAILURE ROLLBACK; WHENEVER OSERROR EXIT FAILURE ROLLBACK; exec ODS.PROC_${tabnm}(to_date(${dataDate},'YYYYMMDD')); exit; EOF echo "存储过程调用完成!" if [ -f ${dataBad} /${tabnm} .BAD ]; then sqlplus ${dbuser} /${dbpwd} @${dbip} :${dbport} /${dbsid} > /dev/null << EOF delete from run_dataload_log where as_of_date=to_date('${dataDate}','YYYYMMDD') and tab_name='${tabnm}' and run_name='$0'; commit; insert into run_dataload_log values (to_date('${dataDate}','YYYYMMDD'),'${tabnm}','$0','failed','PROC_${tabnm}程序执行结束,存在REJECT文件,请检查BAD日志文件${dataBad}/${tabnm}.BAD',sysdate); commit; exit ; EOF else sqlplus ${dbuser} /${dbpwd} @${dbip} :${dbport} /${dbsid} > /dev/null << EOF delete from run_dataload_log where as_of_date=to_date('${dataDate}','YYYYMMDD') and tab_name='${tabnm}' and run_name='$0'; commit; insert into run_dataload_log values (to_date('${dataDate}','YYYYMMDD'),'${tabnm}','$0','success','PROC_${tabnm}程序执行结束,无BAD文件',sysdate); commit; exit ; EOF fi echo "data.sh执行完成!"
ods_load.sh
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 if [ $# -ne 1 ]then echo "没有输入日期参数或输入参数不正确!" echo "参数1:当期跑批日期,格式:YYYYMMDD" exit 1 fi dbuser=ods dbpwd=ods dbip=172.xx.xx.xx dbport=1521 dbsid=rpmdb dataDate=$1 dataPath="/u01/db/oradata/RPM_LOAD/RPM_DATA/DAT_DIR/${dataDate} " dataLog="/u01/db/oradata/RPM_LOAD/RPM_DATA/LOG_DIR/${dataDate} " dataBad="/u01/db/oradata/RPM_LOAD/RPM_DATA/BAD_DIR/${dataDate} " shellPath="/u01/db/oradata/RPM_LOAD/RPM_SHELL" logPath="/u01/db/oradata/RPM_LOAD/RPM_SHELL/log/${dataDate} " hisdat="/u01/db/oradata/RPM_LOAD/RPM_DATA/HISDATA" thread=5 echo "dataPath:" ${dataPath} if [ ! -d ${dataLog} ]; then mkdir ${dataLog} else rm -rf ${dataLog} /*.LOG fi if [ ! -d ${dataBad} ]; then mkdir ${dataBad} else rm -rf ${dataBad} /*.BAD fi if [ ! -d ${logPath} ]; then mkdir ${logPath} else rm -rf ${logPath} /*.log fi tmp_fifofile="/tmp/$$.fifo" echo "$tmp_fifofile " mkfifo $tmp_fifofile exec 6<>$tmp_fifofile rm $tmp_fifofile i=0 while (($i <=$thread ))do echo let i=i+1done >&6 while [ 1 = 1 ] do cd ${shellPath} echo "正在读取加载文件名称..." RES_LINE=`wc -l tablename | awk '{print $1}' ` echo "${RES_LINE} 个文件需要加载, 请等待..." while read line1 do filenm=`echo ${line1} | awk -F ':' '{print $2}' | sed "s/YYYYMMDD/${dataDate} /g" ` fileok=`echo ${line1} | awk -F ':' '{print $3}' | sed "s/YYYYMMDD/${dataDate} /g" ` echo ${dataPath} /${fileok} if [ ! -f ${dataPath} /${fileok} ]; then echo "文件缺失在或没有找到:${fileok} " else let RES_LINE=RES_LINE-1 fi sleep 1 done < ${shellPath} /tablename if [ ${RES_LINE} -ne 0 ]; then echo "没有获取到OK文件!" sleep 1 echo "重新检索OK文件!" RES_LINE=`wc -l tablename | awk '{print $1}' ` else echo "所有的OK文件已经就绪,准备加载数据..." break fi done echo "检查文件完成,进行下一项" sed "s/YYYYMMDD/${dataDate} /g" alter_db.sql > alter_db_${dataDate} _temp.sql echo "替换日期完成,进行下一项" sqlplus ${dbuser} /${dbpwd} @${dbip} :${dbport} /${dbsid} < ${shellPath} /alter_db_${dataDate} _temp.sql >> ${logPath} /alter_db.log 2>&1 echo "修改外部表结构完成,进行下一项" rm ${shellPath} /alter_db_${dataDate} _temp.sql while read line2 do tabnm=`echo ${line2} | awk -F':' '{print $1}' ` sh ${shellPath} /data_load.sh ${tabnm} ${dataDate} ${fileok} >> ${logPath} /dateload_sh.log run_rs=$? if [ ${run_rs} = 0 ]; then echo "${tabnm} 表${dataDate} 期数据加载完成!" sqlplus ${dbuser} /${dbpwd} @${dbip} :${dbport} /${dbsid} > /dev/null << EOF delete from run_dataload_log where as_of_date=to_date('${dataDate}','YYYYMMDD') and tab_name='${tabnm}' and run_name='$0'; commit; insert into run_dataload_log values (to_date('${dataDate}','YYYYMMDD'),'${tabnm}','$0','success','data_load.sh脚本执行成功,数据加载完成',sysdate); commit; exit ; EOF else echo "${tabnm} 表${dataDate} 期数据加载失败,data.sh程序报错,请检查!" sqlplus ${dbuser} /${dbpwd} @${dbip} :${dbport} /${dbsid} > /dev/null << EOF delete from run_dataload_log where as_of_date=to_date('${dataDate}','YYYYMMDD') and tab_name='${tabnm}' and run_name='$0'; commit; insert into run_dataload_log values (to_date('${dataDate}','YYYYMMDD'),'${tabnm}','$0','failed','data_load.sh脚本执行失败,请检查日志:${logPath}/date_sh.log',sysdate); commit; exit ; EOF fi echo "调用data_load.sh程序完成!" done < ${shellPath} /tablename echo "所有数据加载完成,开始数据文件备份..." tar -cf - ${dataPath} | gzip > ${hisdat} /${dataDate} .tar.gz echo "程序结束!" wait exec 6>&-exit 0
参考资料 Linux 变量的使用 理解 inode