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 release 6.9 (Final) bash, version 4.1.2(2)
CentOS Linux release 7.4.1708 (Core) bash, version 4.2.46(2)

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 release 6.9 (Final) bash, version 4.1.2(2)
CentOS Linux release 7.4.1708 (Core) bash, version 4.2.46(2)

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
[root@centos7 service]#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?表达式}表达式输出至stderrorvar=var=$str
var=${str:?表达式}表达式输出至stderror表达式输出至stderrorvar=$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 ] [ [ -[f|b] ] 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 变量名 [lindex $argv 数字]

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

#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]

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 Jan  6 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 Jan  6 16:13:17 2018 from 192.168.4.101
HI,hunk, Time is 2018-01-06#16:13:53
echo 'hello'
date
exit
[hunk@centos6 ~]$echo 'hello'
hello
[hunk@centos6 ~]$date
Sat Jan  6 16:13:53 CST 2018
[hunk@centos6 ~]$exit
logout
Connection to 192.168.4.100 closed.

bash脚本中调用expect

关键点是使用多行重定向

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

expect << EOF
set timeout 10
spawn ssh $user@$ip
expect {
    "yes/no" { send "yes\n";exp_continue }
    "password" { send "$pass\n"}
}
expect "HI" { send "echo 'hello'\n" }
expect "llo" { send "date\n"}
send "exit\n"
expect eof

EOF

实例

使用ftp自动上传文件

#!/bin/bash
ip=192.168.4.100
user=hunk
pass=passwd1234
expect  << EOF
set timeout 10
spawn ftp $ip
expect {
    "Name" { send "$user\n";exp_continue }
    "Password" { send "$pass\n"}
}
expect "ftp>" { send "put ftp.sh\n" }
expect "ftp>" { send "ls\n" }
expect "ftp>" { send "bye\n" }
expect eof
EOF

运行结果

#bash ftp.sh 
spawn ftp 192.168.4.100
Connected to 192.168.4.100 (192.168.4.100).
220 (vsFTPd 2.2.2)
Name (192.168.4.100:root): hunk
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> put 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<=$level;i++));do echo -n $path;done`
                echo -e "生成的创建的相对路径软链接的命令为:
                \033[1;33mln -s $newscr$src $dec \033[0m"

        fi

运行结果

#bash 2create_symbolic.sh
必须2个参数,语法:2create_symbolic.sh 源文件绝对路径 软链接绝对路径

#bash create_symbolic.sh app/a/b/c/d/link.txt /home/hunk/test/
app/a/b/c/d/link.txt 不存在

#bash create_symbolic.sh /app/a/b/c/d/link.txt home/hunk/test/
home/hunk/test/ 有不存在的目录

#bash create_symbolic.sh /app/a/b/c/d/link.txt /home/hunk/test/aaaa
生成的创建的相对路径软链接的命令为:
        ln -s ../../../app/a/b/c/d/link.txt /home/hunk/test/aaaa 

#bash create_symbolic.sh /app/a/b/c/d/link.txt /home/hunk/test/aaaa/
生成的创建的相对路径软链接的命令为:
        ln -s ../../../app/a/b/c/d/link.txt /home/hunk/test/aaaa 

#bash create_symbolic.sh /app/a/b/c/d/ /home/hunk/test/aaaa/
生成的创建的相对路径软链接的命令为:
        ln -s ../../../app/a/b/c/d/ /home/hunk/test/aaaa 

感觉太绕。不过是考虑到好些情况。

第五章有时间再写吧~~


本文转自 ljpwinxp 51CTO博客,原文链接:http://blog.51cto.com/191226139/2058238


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值