欲忘树 发表于 2018-8-19 08:21:19

Shell 编程进阶(四)

更改字段分隔符
  默认情况下,bash shell会将以下字符当作字段分隔符。
  空格
  制表符 TAB
  换行符\n
  环境变量中有有一个叫IFS的变量用来存放分隔符的。
  

  
这里有一个文件,里面各种格式,我们用for 来遍历里面的数据
  
#cat a.txt
  
i love you
  
who are you
  
root:xxx:/bin/bash
  
abc;cdf;
  

  
运行结果
  
#bash IFS.sh
  
i
  
love
  
you
  
who
  
are
  
you
  
root:xxx:/bin/bash
  
abc;cdf;
  

  可是,这并不是我们预期的效果。
  

碰到这种情况,环境变量IFS就有绝妙的用法。  
假如要遍历一个文件中用冒号:分隔的值,(/etc/passwd).要做的就是将IFS的值设定为
  
IFS=:
  

  
上面的例子中,我们想要的是一句完整的句子,并且把冒号:和分号;的字段做为单独的值,那么IFS就得这样设置
  
IFS=:;
  
这个赋值会将换行符\n 、 冒号:分号;作为字段的分隔符。如何使用IFS字符解析数据没有任何限制。
  

#!/bin/bash  
IFS=:;
  
for i in $(cat a.txt);do
  echo $i
  
done
  

  
注意上面的分隔符中的分号
  
#bash IFS.sh
  
i love you
  
who are you
  
root
  
xxx
  
/bin/bash
  
abc;cdf;
  

  

#!/bin/bash  
#!/bin/bash
  
IFS=:";"
  
for i in $(cat a.txt);do
  echo $i
  
done
  
注意上面的分隔符中的分号,结果是不一样的
  
#bash IFS.sh
  
i love you
  
who are you
  
root
  
xxx
  
/bin/bash
  
abc
  
cdf
  

  
如果想在一个脚本既想用新设置的IFS值,又想在其他地方调用原来的IFS值,建议的做法是:
  
IFS.old=$IFS
  
IFS=新值
  

  

  执行以下代码,可以显示出在环境变量PATH所包含的所有目录中全部的可执行文件,数量还真不行呢
  

#!/bin/bash  
IFS=:
  
for folder in $PATH;do
  echo "$folder"
  for file in $folder/*;do
  [ -x "$file" ] && echo "$file"
  done
  
done
  

  
运行结果
  
...
  
/usr/bin/zipsplit
  
/usr/bin/zless
  
/usr/bin/zmore
  
/usr/bin/znew
  
/usr/bin/zsoelim
  
/root/bin
  
...
  

  
#bash PTAH.sh |wc -l
  
1660
  

字符串切割
   ${#var}
  返回字符串变量var的长度
  

#echo $abc  
abcdefghijklmnopqrstuvwxyz
  
#echo ${#abc}
  
26
  

  

   ${var:offset}
  返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,
  offset的取值在0到 ${#var}-1 之间(bash4.2后,允许为负值)
  

#echo $num  
123456789
  
#echo ${num:3:4}
  
4567
  

  
#echo ${num:3}
  
456789
  

  
注意以下的,并不会以负数的方式处理
  
#echo $num
  
-10-9-8-7-6-5-4-3-2-10123456789
  
#echo ${num:3:2}
  
-9
  

  
不同版本bash的差异

  
CentOS>
  
CentOS Linux>  

  
Centos 7
  
#echo $num
  
123456789
  
#echo ${num: -5:-2}
  
567
  

  
Centos 6
  
#echo $num
  
123456789
  
#echo ${num: -5:-2}
  
-bash: -2: substring expression < 0
  

  

   ${var:offset:number}
  返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
  

#echo $num  
123456789
  
#echo ${num:3:4}
  
4567
  

   ${var:-length}
  取字符串的最右侧几个字符。注意:冒号后必须有一空白字符
  

截取右数N个  
#echo $num
  
123456789
  
#echo ${num: -3}
  
789
  

   ${var:offset:-length}
  从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容
  

去头N个,去尾N个  
#echo $num
  
123456789
  
#echo ${num:2:-2}
  
34567
  

  

   ${var:-length:-offset}
  先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容
  注意:-length前空格
  

先从后截取N个,再从后再截取N个。注意前面的数字一定要比后面的大  
#echo $num
  
123456789
  
#echo ${num: -5:-2}
  
567
  

  
不同版本bash的差异

  
CentOS>
  
CentOS Linux>  

  
Centos 7
  
#echo $num
  
123456789
  
#echo ${num: -5:-2}
  
567
  

  
Centos 6
  
#echo $num
  
123456789
  
#echo ${num: -5:-2}
  
-bash: -2: substring expression < 0
  

字符串处理
  基于模式取子串
   ${var#*word}
  方向流 》
  其中word可以是指定的任意字符
  功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符之间的所有字符.包括word也删除
  

#echo $num  
123456789
  
#echo ${num#*5}
  
6789
  

  
找到的第一个字符串
  
#echo $var
  
root:x:0:0:,,00000000:/root:/bin/bash
  
#echo ${var#*root}
  
:x:0:0:,,00000000:/root:/bin/bash
  
------------------------------------------------
  
#echo $url
  
https://www.baidu.com/?tn=98012088_5_dg&ch=12
  

  
这个?号代表一个字符
  
#echo ${url#*?}
  
ttps://www.baidu.com/?tn=98012088_5_dg&ch=12
  

  
转义之后的?号
  
#echo ${url#*\?}
  
tn=98012088_5_dg&ch=12
  

   ${var##*word}
  贪婪模式 方向流 》
  功能:自左而右,查找var变量所存储的字符串中,删除字符串开头至最后一次由word指定的字符之间的所有内容,包括word也删除
  

#echo $var  
root:x:0:0:,,00000000:/root:/bin/bash
  
#echo ${var##*root}
  
:/bin/bash
  

  

   ${var%word*}
  方向流 《
  其中word可以是指定的任意字符;
  功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符之间的所有字符;
  

#echo $url  
https://www.baidu.com/?tn=98012088_5_dg&ch=12
  
#echo ${url%\?*}
  
https://www.baidu.com/
  

  

   ${var%%word*}
  贪婪模式方向流 《
  功能:自右而左,查找var变量所存储的字符串中, 删除从最后一个字符向左至最后出现word字符之间的所有字符
  

#echo $var  
root:x:0:0:,,00000000:/root:/bin/bash
  
#echo ${var%%/*}
  
root:x:0:0:,,00000000:
  

  
#echo $url
  
https://www.baidu.com/?tn=98012088_5_dg&ch=12
  
#echo ${url%%?*}
  

  
#echo ${url%%\?*}
  
https://www.baidu.com/
  

  

#echo ${url2##:*}  
http://www.magedu.com:80
  
#echo ${url2##*:}
  
80
  
#echo ${url2%%:*}
  
http
  

  查找替换
   ${var/pattern/substr}
  查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
  

#echo $var  
root:x:0:0:,,00000000:/root:/bin/bash
  
#echo ${var/root/hunk}
  
hunk:x:0:0:,,00000000:/root:/bin/bash
  

   ${var//pattern/substr}
  贪婪模式
  查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
  

#echo $var  
root:x:0:0:,,00000000:/root:/bin/bash
  
#echo ${var//root/hunk}
  
hunk:x:0:0:,,00000000:/hunk:/bin/bash
  

   ${var/#pattern/substr}
  查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
  

这里的行首不要和正则表达式的^搞混哦  
#echo $url3
  
http://ilovelinux.tech http://www.baidu.com
  
#echo ${url3/#http/ftp}
  
ftp://ilovelinux.tech http://www.baidu.com
  

   ${var/%pattern/substr}
  查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
  

这里的行首不要和正则表达式的$搞混哦,而且语法位置不一样  
echo $url3
  
http://ilovelinux.tech http://www.baidu.com http
  
#echo ${url3/%http/ftp}
  
http://ilovelinux.tech http://www.baidu.com ftp
  

  查找并删除
   ${var/pattern}
  删除var所表示的字符串中第一次被pattern所匹配到的字符串
  

#echo $var  
root:x:0:0:,,00000000:/root:/bin/bash
  
#echo ${var/root}
  
:x:0:0:,,00000000:/root:/bin/bash
  

   ${var//pattern}
  贪婪模式
  删除var所表示的字符串中所有被pattern所匹配到的字符串
  

#echo $var  
root:x:0:0:,,00000000:/root:/bin/bash
  
#echo ${var//root}
  
:x:0:0:,,00000000:/:/bin/bash
  

   ${var/#pattern}
  删除var所表示的字符串中所有以pattern为行首所匹配到的字符串
  

#echo $var2  
@@@@root@root:x:0:0:,,00000000:/root:/bin/bash$root$$$
  
#echo ${var2/#@}
  
@@@root@root:x:0:0:,,00000000:/root:/bin/bash$root$$$
  

  
scr=/app/a/b/c/d/link.txt
  
echo ${scr/#'/'}
  
app/a/b/c/d/link.txt
  

   ${var/%pattern}
  删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
  

#echo $var2  
@@@@root@root:x:0:0:,,00000000:/root:/bin/bash$root$$$
  
#echo ${var2/%$}
  
@@@@root@root:x:0:0:,,00000000:/root:/bin/bash$root$$
  

  字符大小写转换
   ${var^^}
  把var中的所有小写字母转换为大写
  

#echo $var  
root:x:0:0:,,00000000:/root:/bin/bash
  
#echo ${var^^}
  
ROOT:X:0:0:,,00000000:/ROOT:/BIN/BASH
  

   ${var,,}
  把var中的所有大写字母转换为小写
  

#echo $var  
ROOT:X:0:0:,,00000000:/ROOT:/BIN/BASH
  
#echo ${var,,}
  
root:x:0:0:,,00000000:/root:/bin/bash
  

  变量赋值的特别
  变量有3种状态,准确来说,是2种。
  1.没有声明
  2.有声明,值为空
  3.有声明,有值

变量配置方式
str为未声明变量
str变量空值
str变量有值
var=${str-表达式}
var=表达式
var=
var=$str
var=${str:-表达式}
var=表达式
var=表达式
var=$str
var=${str+表达式}
var=
var=表达式
var=表达式
var=${str:+表达式}
var=
str不变 var=
var=表达式
var=${str=表达式}
str=表达式 var=表达式
str不变 var=
str不变 var=$str
var=${str:=表达式}
str=表达式 var=表达式
str=表达式 var=表达式
str不变 var=$str
var=${str?表达式}
表达式输出至stderror
var=
var=$str
var=${str:?表达式}
表达式输出至stderror
表达式输出至stderror
var=$str  举2个例说明下:
  

  
unset str
  
var=${str-"abc"}
  
echo "var=$var"
  

  
运行结果
  
var=abc
  

  
str=
  
var=${str-"abc"}
  
echo "var=$var"
  

  
运行结果
  
var=
  

  
unset str
  
str='str有值'
  
var=${str-"abc"}
  
echo "var=$var"
  

  
运行结果
  
var=str有值
  

  

#unset str  
#var=${str:?"abc"}
  
#echo "var=$var"
  

  
运行结果
  
a.sh: line 3: str: abc
  

  
#str=
  
#var=${str:?"abc"}
  
#echo "var=$var"
  
运行结果
  
a.sh: line 7: str: abc
  

  
unset str
  
str='str有值'
  
var=${str:?"abc"}
  
echo "var=$var"
  

  
运行结果
  
var=str有值
  

  

  高级变量用法-有类型变量
   Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的。typeset 是属于将被淘汰的命令队列中。
  

declare [选项] 变量名  

  
-r 声明或显示只读变量
  
-i 将变量定义为整型数
  
-a 将变量定义为数组
  
-A 将变量定义为关联数组
  
-f 显示已定义的所有函数名及其内容
  
-F 仅显示已定义的所有函数名
  
-x 声明或显示环境变量和函数
  
-l 声明变量为小写字母 declare –l var=UPPER
  #declare -l var=AAA
  #echo $var
  aaa
  

  
-u 声明变量为大写字母 declare –u var=lower
  #declare -u var=aaa
  #echo $var
  AAA
  

  如果变量一但声明为有类,那么赋值的时候就要特别注意了。
  #unset var
  #declare -i var
  #var=10;echo $var
  10
  #var=abc;echo $?;echo $var
  0   》这里返回的上条命令的结果是0
  0   》 但是var的值却是0,因为这是一个int的变量,却存了字符类型
  

  

eval 命令
  eval 命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量.该命
  令对变量进行两次扫描
  

#n=10  
#echo {1..$n}
  
{1..10}
  
#eval echo {1..10}
  第一次扫描时,会将 $n 置换成 10
  第二次扫描时,会执行echo 命令
  
结果如下:
  
1 2 3 4 5 6 7 8 9 10
  

  

间接变量引用
  如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用
   var1的值是var2,而var2又是变量名,var的值为value,间接变量引用是指通过var1获得变量值value的行为
  

bash Shell提供了两种格式实现间接变量引用  
第一种写法
  

  
#who=user
  
#user=hunk
  
#eval echo \$$who    \是必须的,否则$$代表的是当前进程号
  eval 第一次扫描时,置换为 eval echo $user
  eval 第二次扫描时,执行echo $user
  
结果
  
hunk
  

  
第二种写法
  

  
#echo ${!who}
  
hunk
  

  
第三种写法:
  
#who=$user
  
#user=hunk
  
#echo $who
  
结果
  
hunk
  

expect
  expect 是由Don Libes基于Tcl( Tool Command Language)语言开发的,主要应用于自动化交互式操作的场景,借助
  Expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对
  多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率
  

expect 语法:  
expect [选项] [ -c cmds ] [ [ - ] cmdfile ] [ args ]
  

  
-c:从命令行执行expect脚本,默认expect是交互地执行的
  

  
示例:expect -c 'expect "\n" {send "显示这里的字符"}'
  #expect -c 'expect "\n" {send "显示这里的字符"}'
  捕抓到回车
  显示这里的字符
  
-d:可以输出输出调试信息
  
示例:expect -d ssh.exp
  

  
expect中相关命令
  spawn:启动新的进程
  send:用于向进程发送字符串
  expect:从进程接收字符串
  一般特征 字符串往往是等待输入的最后的提示符的特征信息
  interact:允许用户交互
  exp_continue 匹配多个字符串在执行动作后加此命令
  

  expect最常用的语法(tcl语言:模式-动作)
  

#!/usr/local/bin/expect -f  

  
 单一分支模式语法:
  expect “hi” {send “You said hi\n"}
  匹配到包含 hi 后,会输出“you said hi”,并换行
  

  
 多分支模式语法:
  expect "hi" { send "You said hi\n" } "hehe" { send "Hehe yourself\n" }"bye" { send "Good bye\n" }
  匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:
  
expect {
  
"hi" { send "You said hi\n"}
  
"hehe" { send "Hehe yourself\n"}
  
"bye" { send “Good bye\n"}
  
}
  

  
这不是不循环,倒累似于if 判断,判断完了就退出了
  

  示例:
  实现scp的自动登录并复制文件
  

#ssh 192.168.4.100  
The authenticity of host '192.168.4.100 (192.168.4.100)' can't be established.
  
RSA key fingerprint is SHA256:FlJ8SyKInGHEFpnwuhdCooAgVxRtxJ4hNKO2Fzd34xA.
  
RSA key fingerprint is MD5:4b:58:95:8f:82:d5:44:e9:d7:83:0b:43:3e:55:5e:98.
  
Are you sure you want to continue connecting (yes/no)?
  

  
以上是默认第一次远程连接的时候,需要用户输入 yes/no。如果用expect来捕获,最好复制关键字符串,而不是自己键入,以保证准确率。
  

  
#!/usr/bin/expect                                          这里的与bash脚本有差异
  
spawn scp /tmp/b.log root@192.168.4.100:/tmp   启动一个新的进程,这个进程就是scp
  
expect {                                                          这里是代码块开始
  "yes/no" {send "yes\n";exp_continue }    捕获 "yes/no"关键字时,向进程scp发送"yes"并回车
  "password" {send "passwd1234\n" }       捕获 "password"关键字时,向进程scp发送"passwd1234"并回车
  
}
  
expect eof                                    这里是代码块的结束
  

  
建议将脚本保存为exp结尾,以便与bash脚本区别
  

  
执行结果
  
#expect ssh1.exp
  

  
spawn scp /tmp/b.log hunk@192.168.4.100:/tmp
  
The authenticity of host '192.168.4.100 (192.168.4.100)' can't be established.
  
RSA key fingerprint is SHA256:FlJ8SyKInGHEFpnwuhdCooAgVxRtxJ4hNKO2Fzd34xA.
  
RSA key fingerprint is MD5:4b:58:95:8f:82:d5:44:e9:d7:83:0b:43:3e:55:5e:98.
  
Are you sure you want to continue connecting (yes/no)? yes
  
Warning: Permanently added '192.168.4.100' (RSA) to the list of known hosts.
  
hunk@192.168.4.100's password:
  
b.log                                                                                          100%   39    41.3KB/s   00:00
  

  

  使用变量
  

语法:  
set 变量名 值
  

  
#!/usr/bin/expect
  
set ip 192.168.4.100
  
set user hunk
  
set passwd passwd1234
  
set timeout 10
  

  
spawn ssh $user@$ip
  
expect {
  "yes/no" {send "yes\n";exp_continue }
  "password" {send "$passwd\n" }
  
}
  
interact
  
expect eof
  

  位置参数
  

语法:  
set 变量名
  

  
$argv 0 代表第一个位置参数,$argv 1 代表第二个... 注意别和bash的位置参数搞混了
  
参数位置是相对于执行程序本身的位置,而不是相对变量
  

  
#!/usr/bin/expect
  
set ip
  
set user
  
set pass
  

  
spawn ssh $user@$ip
  
expect {
  "yes/no" { send "yes\n";exp_continue }
  "password" { send "$pass\n"}
  
}
  
interact这个指令是使用交换模式
  

  
执行命令
  

  
#expect ssh2.exp 192.168.4.100 hunk passwd1234
  
spawn ssh hunk@192.168.4.100
  
hunk@192.168.4.100's password:
  
Last login: Sat Jan6 15:39:46 2018 from 192.168.4.101
  

#!/usr/bin/expect  
#设置变量
  
set ip 192.168.4.100
  
set user hunk
  
set pass passwd1234
  
set timeout 10
  
#启动新的ssh进程
  
spawn ssh $user@$ip
  
#ssh 自动验证
  
expect {
  "yes/no" { send "yes\n";exp_continue }
  "password" { send "$pass\n"}
  
}
  
#登录后执行2条命令
  
expect "HI" { send "echo 'hello'\n" }
  
expect "llo" { send "date\n"}
  

  
#发送exit并回车
  
send "exit\n"
  
expect eof
  

  
执行结果
  
#expect ssh3.exp
  
spawn ssh hunk@192.168.4.100
  
hunk@192.168.4.100's password:
  
Last login: Sat Jan6 16:13:17 2018 from 192.168.4.101
  
HI,hunk, Time is 2018-01-06#16:13:53
  
echo 'hello'
  
date
  
exit
  
$echo 'hello'
  
hello
  
$date
  
Sat Jan6 16:13:53 CST 2018
  
$exit
  
logout
  
Connection to 192.168.4.100 closed.
  

  bash脚本中调用expect
  

关键点是使用多行重定向  

  
#!/bin/bash
  
ip=$1
  
user=$2
  
pass=passwd1234
  
#下面为使用多行重定向执行
  

  
expectput ftp.sh
  
local: ftp.sh remote: ftp.sh
  
227 Entering Passive Mode (192,168,4,100,106,68).
  
150 Ok to send data.
  
226 Transfer complete.
  
294 bytes sent in 8.2e-05 secs (3585.37 Kbytes/sec)
  
ftp> ls
  
227 Entering Passive Mode (192,168,4,100,193,210).
  
150 Here comes the directory listing.
  
-rw-r--r--    1 500      500         294 Jan 06 09:45 ftp.sh
  
226 Directory send OK.
  
ftp> bye
  
221 Goodbye.
  

  生成相对路径的软链接:
  

#!/bin/bash  
path='../'
  
#判断参数
  if [$# -ne 2 ];then
  echo "必须2个参数,语法:`basename $0` 源文件绝对路径 软链接绝对路径" && exit 10
  elif [ ! -e "$1" ];then
  echo "$1 不存在" && exit 20
  elif [ ! -d `dirname $2` ];then
  echo "$2 有不存在的目录" && exit 30
  elif [[ "$1" =~ ^[^/] ]];then
  echo "$1 不是绝对路径" && exit 40
  elif [[ "$2" =~ ^[^/] ]];then
  echo "$2 不是绝对路径" && exit 50
  else
  dec=${2/%'/'}
  [ -d "$dec" ] && echo "$dec 是目录,请换成文件名" && exit 60
  #定义软链接绝对路径到根目录的层数
  level=`dirname $2 | grep -o '/'|wc -l`
  #定义去除源文件中首个/
  src=${1/#'/'}
  #定义去除软链接绝对路径中尾部的/
  #定义生成相对路径
  newscr=`for (( i=1; i
页: [1]
查看完整版本: Shell 编程进阶(四)