bash
shell是一个解释器,启动器
解释器:
用户交互输入
文本文件输入
头部定义脚本解释器:
#! /bin/bash
#! /usr/bin/python
读取方式:
当前shell进程执行:
source(.)
[root@node01 shell]# vi s.sh
[root@node01 shell]# cat s.sh
echo "hello word!!"
echo "进程号:$$"
[root@node01 shell]# echo $$
1469
[root@node01 shell]# source s.sh #进程id与当前进程 id相同
hello word!!
进程号:1469
[root@node01 shell]# . s.sh
hello word!!
进程号:1469
新建子shell进程执行:
./filename 或 /bin/bash ./filename
[root@node01 shell]# sed -i '1i\#!/bin/bash' s.sh #添加前缀,指定解释器
[root@node01 shell]# cat s.sh
#!/bin/bash
echo "hello word!!"
echo "进程号:$$"
[root@node01 shell]# chmod +x ./s.sh #给脚本文件加上执行权限
[root@node01 shell]# echo $$
1469
[root@node01 shell]# ./s.sh #进程id与父进程不同
hello word!!
进程号:1527
[root@node01 shell]# /bin/bash ./s.sh
hello word!!
进程号:1528
函数:
[root@node01 shell]# pt ()
> {
> echo "进程号:$$"
> }
[root@node01 shell]# pt
进程号:1105
[root@node01 shell]# type pt #pt为函数类型
pt is a function
pt ()
{
echo "进程号:$$"
}
变量:
本地:
当前shell进程拥有;
生命周期随shell进程;
# 下面验证:子进程消失后,其变量是否消失
[root@node01 shell]# pstree
init─┬─auditd───{auditd}
├─crond
├─dbus-daemon
├─master─┬─pickup
│ └─qmgr
├─6*[mingetty]
├─rsyslogd───3*[{rsyslogd}]
├─sshd───sshd───bash───pstree
└─udevd───2*[udevd]
[root@node01 shell]# /bin/bash #创建一个子进程
[root@node01 shell]# pstree
init─┬─auditd───{auditd}
├─crond
├─dbus-daemon
├─master─┬─pickup
│ └─qmgr
├─6*[mingetty]
├─rsyslogd───3*[{rsyslogd}]
├─sshd───sshd───bash───bash───pstree #多一层bash
└─udevd───2*[udevd]
[root@node01 shell]# city=changsha #子进程里创建变量
[root@node01 shell]# echo $city
changsha
[root@node01 shell]# exit #退出子进程
exit
[root@node01 shell]# pstree
init─┬─auditd───{auditd}
├─crond
├─dbus-daemon
├─master─┬─pickup
│ └─qmgr
├─6*[mingetty]
├─rsyslogd───3*[{rsyslogd}]
├─sshd───sshd───bash───pstree #子进程已删除
└─udevd───2*[udevd]
[root@node01 shell]# echo $city #打印无结果
局部:
只能用于函数
例:local val=100
[root@node01 shell]# localtest () {
> local attr=4396 #创建一个局部变量
> echo $attr
> }
[root@node01 shell]# localtest #调用方法有结果
4396
[root@node01 shell]# echo $attr #外部调用无结果
位置:
脚本、函数下
$1,$2,${11}
特殊:
$#:位置参数个数
$*:参数列表,双引号引用为一个字符串
$@:参数列表,双引号引用为单独的字符串
[root@node01 shell]# cat t.sh
#!/bin/bash
echo "参数列表:$*"
echo "总共 $# 个参数"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "第十一个参数:${11}"
[root@node01 shell]# ./t.sh a b c d e f g h i j k l m n
参数列表:a b c d e f g h i j k l m n
总共 14 个参数
第一个参数:a
第二个参数:b
第十一个参数:k
[root@node01 shell]# cat ./diff.sh
#!/bin/bash
echo '$*: '$*
echo '$@: '$@
echo '$* for: '
for i in "$*"; do
echo $i;
done
echo '$@ for: '
for i in "$@"; do
echo $i;
done
[root@node01 shell]# ./diff.sh a b c d e f #$*与$@的区别
$*: a b c d e f
$@: a b c d e f
$* for:
a b c d e f
$@ for:
a
b
c
d
e
f
$$:当前shell的PID:在管道中会特殊处理,看下面例子$BASHPID:真实PID
$?:上一个命令退出状态
0:成功
other:失败
变量传递:
首先看例子:
[root@node01 shell]# s=99 #父进程中定义变量
[root@node01 shell]# s=100 | echo ok #管道中修改
ok
[root@node01 shell]# echo $s #父进程中变量未改变
99
s变量未被修改,这是为什么呢?
因为在管道的左边的命令,并不是在主进程先执行完再把结果通过管道传递给另外一个命令;
而是bash会 创建一个子进程 来执行左边的命令,创建 另外一个子进程 来执行右边的命令,再把左边的输出流跟右边的输入流连接起来
这就解释为什么s变量未被修改,但是再看下面例子:
[root@node01 shell]# echo $$
1469
[root@node01 shell]# echo $$ | cat
1469
两个pid一样,这不是与前面矛盾吗? 再看一个例子:
[root@node01 shell]# echo $$
1469
[root@node01 shell]# echo $BASHPID
1469
[root@node01 shell]# echo $BASHPID | cat
1494
[root@node01 shell]# echo $BASHPID | cat
1496
这个就与预期相同了(子进程的pid不一样),这是为什么呢?
因为bash会把$$做特殊处理,再提交这个命令之前,父进程就会把$$的值给赋值进去
而对于其他的变量,则会让子进程去处理解释
bash默认不会传递用户变量到子进程,需要 export 导出:
[root@node01 shell]# vi fork.sh #创建一个脚本
[root@node01 shell]# cat fork.sh #脚本里输出city变量
#!/bin/bash
echo $city
[root@node01 shell]# chmod +x ./fork.sh
[root@node01 shell]# city=changsha #父进程定义一个city变量
[root@node01 shell]# echo $city #父进程能输出
changsha
[root@node01 shell]# ./fork.sh #子进程不能输出
[root@node01 shell]# export city #导出city变量
[root@node01 shell]# ./fork.sh #子进程能输出
changsha
那父进程与子进程为值传递还是引用传递?经测试为值传递
也就是
父进程修改变量不会改变子进程变量值
子进程修改变量不会改变父进程变量值
准确的说shell变量为:
修改时复制(copy on write):就是当变量没有被修改时父子进程共用一个内存地址的变量,但父子进程 修改了变量时,子进程 会拷贝一份
重定向
程序自身都有I/O
linux中一切皆文件,这是linux的设计思想,优雅之处
看个演示:
[root@node01 ~]# cd /proc
[root@node01 proc]# ls
1 1099 14 20 29 5 936 dma kcore mounts softirqs vmstat
10 11 146 21 3 6 974 driver keys mpt stat zoneinfo
1050 1100 147 22 30 620 acpi execdomains key-users mtd swaps
1059 1102 15 23 31 664 asound fb kmsg mtrr sys
1060 1126 150 24 32 665 buddyinfo filesystems kpagecount net sysrq-trigger
1086 1326 151 25 33 697 bus fs kpageflags pagetypeinfo sysvipc
1088 12 152 26 351 7 cgroups interrupts loadavg partitions timer_list
[root@node01 proc]# echo $$
1326
进入proc内核文件夹,里面又很多数字文件夹,每个数字就代表一个进程号;
例如当前的连接终端terminal,1326进程就在里面有个文件夹;
[root@node01 proc]# cd /proc/$$
[root@node01 1326]# ls
attr cmdline environ limits mounts oom_adj root stack task
autogroup comm exe loginuid mountstats oom_score sched stat wchan
auxv coredump_filter fd maps net oom_score_adj schedstat statm
cgroup cpuset fdinfo mem ns pagemap sessionid status
clear_refs cwd io mountinfo numa_maps personality smaps syscall
进入terminal进程的文件夹
[root@node01 1326]# cd fd
[root@node01 fd]# ll
total 0
lrwx------ 1 root root 64 Jun 27 19:31 0 -> /dev/pts/2
lrwx------ 1 root root 64 Jun 27 19:33 1 -> /dev/pts/2
lrwx------ 1 root root 64 Jun 27 19:33 2 -> /dev/pts/2
lrwx------ 1 root root 64 Jun 27 19:33 255 -> /dev/pts/2
进入fd文件流目录
程序自身都有I/O
0:标准输入
1:标准输出
2:错误输出每个IO对应一个数字文件,例如 程序对文件开启一个IO流,就会有一个数字3;
这些IO输入输出流在这里指向了不同的地方,就实现了解耦;
发现这些输出都指向一个 /dev/pts 文件夹下的一个文件;
[root@node01 fd]# cd /dev/pts
[root@node01 pts]# ll
total 0
crw--w---- 1 root tty 136, 0 Jun 27 17:22 0
crw--w---- 1 root tty 136, 1 Jun 27 17:23 1
crw--w---- 1 root tty 136, 2 Jun 27 20:01 2
c--------- 1 root root 5, 2 Jun 27 17:15 ptmx
这个 /dev/pts 是一个虚拟终端的文件夹,每个终端(连接)对应一个数字
所有每个ssh连接的输出都输出到了终端上
这个就是打开了 0,1,2三个终端,
新建一个ssh连接,就会有个数字3的终端
控制程序I/O位置:
A、重定向ls的标准输出到文件
[root@node01 shell]# ll
total 12
-rwxr-xr-x 1 root root 23 Jun 21 15:23 fork.sh
-rwxr-xr-x 1 root root 54 Jun 21 16:23 s.sh
-rwxr--r-- 1 root root 155 Jun 21 15:08 t.sh
[root@node01 shell]# ls ./ 1> ~/ls.txt
[root@node01 shell]# cat ~/ls.txt
fork.sh
s.sh
t.sh
B、利用重定向也可以对网络流实现类似操作文件的操作
[root@node01 ~]# type exec
exec is a shell builtin
[root@node01 ~]# exec ls ./shell
fork.sh s.sh t.sh
Connection closing...Socket close.
Connection closed by foreign host.
Disconnected from remote host(node01) at 00:05:34.
Type `help' to learn how to use Xshell prompt.
[g:\~]$
命令:exec
发现ls执行完,输出了结果,ssh就退出了,这是因为:
exec会把后面的命令的代码在内存中压栈到当前进程的内存栈
而不是另外启动一个进程
所以当前ls执行完后,关闭ls进程就把ssh进程关闭了
[root@node01 fd]# exec 8<> /dev/tcp/www.baidu.com/80
[root@node01 fd]# ll
total 0
lrwx------ 1 root root 64 Jun 27 19:31 0 -> /dev/pts/2
lrwx------ 1 root root 64 Jun 27 19:33 1 -> /dev/pts/2
lrwx------ 1 root root 64 Jun 27 19:33 2 -> /dev/pts/2
lrwx------ 1 root root 64 Jun 27 19:33 255 -> /dev/pts/2
lrwx------ 1 root root 64 Jun 27 20:32 8 -> socket:[10492]
利用exec,让exec去给当前进程绑定一个文件操作符8的输入输出流到一个文件(百度的80端口)
查看fd目录,发现8号绑定到一个socket
(linux中一切皆文件,这里一个socket链接也可以是文件)
[root@node01 fd]# echo -e "GET / HTTP/1.1\n" 1>& 8
让echo的标准输出1重定向到8
1>& 8 文件操作符之间的重定向需要加 & 符
这里就是给百度通过socket流(8)发送了一个首页的http请求
[root@node01 fd]# cat 0<& 8
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: no-cache
Connection: Keep-Alive
Content-Length: 14615
Content-Type: text/html
Date: Wed, 27 Jun 2018 16:26:21 GMT
Last-Modified: Mon, 11 Jun 2018 11:19:00 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Pragma: no-cache
Server: BWS/1.1
Set-Cookie: BAIDUID=5512413AB9FEB431236D789E99F654A9:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=5512413AB9FEB431236D789E99F654A9; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1530116781; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
<!DOCTYPE html><!--STATUS OK-->
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
<link rel="dns-prefetch" href="//s1.bdstatic.com"/>
<link rel="dns-prefetch" href="//t1.baidu.com"/>
<link rel="dns-prefetch" href="//t2.baidu.com"/>
<link rel="dns-prefetch" href="//t3.baidu.com"/>
<link rel="dns-prefetch" href="//t10.baidu.com"/>
<link rel="dns-prefetch" href="//t11.baidu.com"/>
<link rel="dns-prefetch" href="//t12.baidu.com"/>
<link rel="dns-prefetch" href="//b1.bdstatic.com"/>
<title>百度一下,你就知道</title>
用cat,将cat的的标准输入0重定向给8,
这样就接受到了百度返回的结果,
实现了对文件操作而打开了网页
C:标准输出、错误输出给混合输出
[root@node01 shell]# ls ./noexist #noexist是一个不存在的目录
ls: cannot access ./noexist: No such file or directory
[root@node01 shell]# ls ./ ./noexist 1> ~/ls.txt
ls: cannot access ./noexist: No such file or directory
让ls输出两个目录 ./ ./noexist
让ls的标准输出重定向到文件
这时错误输出没有重定向,仍然输出到屏幕
[root@node01 shell]# ls ./ ./noexist 1> ~/ls.txt 2> ls-error.txt
让标准输出,错误输出分别重定向输出到不同的文件,这时屏幕没有输出了
这时想让标准输出,错误输出输出到一个文件呢?
[root@node01 shell]# ls ./ ./noexist 1> ~/ls.txt 2>& 1
[root@node01 shell]# cat ~/ls.txt
ls: cannot access ./noexist: No such file or directory
./:
fork.sh
ls-error.txt
s.sh
t.sh
让标准输出1重定向到文件,在让 错误输出2重定向到1,这样就行了
注意这里顺序不能反,重定向到文件须在前,也就是 多个重定向时,重定向是有顺序的
[root@node01 shell]# ls ./ ./noexist 2>& 1 1> ~/ls.txt
ls: cannot access ./noexist: No such file or directory
为什么会这样呢?
因为首先2重定向到1,这时1指向终端,所以2输出到了终端
然后才是重定向1输出到文件
上面混合输出的简写(>&),只写一个 > 相当于 1>,只重定向标准输出
[root@node01 shell]# ls ./ ./noexist >& ~/ls.txt
[root@node01 shell]# cat ~/ls.txt
ls: cannot access ./noexist: No such file or directory
./:
fork.sh
ls-error.txt
s.sh
tool.sh
t.sh
D:重定向输入流演示
[root@node01 shell]# read dengchao #这时会阻塞,让你输入
goodman
[root@node01 shell]# echo $dengchao
goodman
read命令:
read会读取你的输入然后赋值给后面的命令
[root@node01 shell]# read dengchao 0<<<badman
[root@node01 shell]# echo $dengchao
badman
这里将字符串重定向给read的标准输入
<<< : 是将 一段字符串 重定向
[root@node01 shell]# cat 0<<IMBORDER
> asfnnaknf
> dasadsdas
> asddasdad
> IMBORDER
asfnnaknf
dasadsdas
asddasdad
将cat的输入流重定向给一个边界字符输入流
就是 利用 << (两个<符) 后面接一个自定义的 边界字符串
后面会让你输入
当读到你定义的边界符时,就会关闭输入流
利用这个可以做一些实用的操作,例如脚本里输出的用户帮助:
#!/bin/bash
cat 0<<BORDER
欢迎使用本脚本工具
使用方法(输入下列数字回车):
1、查看IP
2、查看内存
。。。
BORDER
read toolId
echo "你选择了选项:$toolId"
[root@node01 shell]# ./tool.sh
欢迎使用本脚本工具
使用方法(输入下列数字回车):
1、查看IP
2、查看内存
。。。
2
你选择了选项:2
这样就不同对每行的提示,分别用echo输出,直接用cat就输出了
bash不会对后面的字符串进行解释,七步扩展,直到遇到边界符
相关文章: