更改字段分隔符
默认情况下,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?表达式} | 表达式输出至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 ] [ [ -[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 [email protected]:/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 [email protected]:/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.
[email protected]'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 [email protected]
[email protected]'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 [email protected]
[email protected]'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
感觉太绕。不过是考虑到好些情况。
第五章有时间再写吧~~