1、bash shellhtml
能够理解为一种解释器和启动器,解释命令文本,并执行命令。java
命令来源:python
1.示例,写一个最简单的文本nginx
vi test.txt
写入如下内容:chrome
echo "Hello World" ls -l / echo $$
这样就写好了一个最简单的脚本,其中$$表示运行当前脚本的bash的进程号。shell
2.如何运行这个文本中的命令express
# 使用bash内建命令source来运行 source test.txt # 使用"."来运行,注意点后面是空格 . test.txt
实际上source和"."都是bash的biuldin命令:centos
[root@centos-clone1 ~]# type source source is a shell builtin [root@centos-clone1 ~]# type '.' . is a shell builtin
source和"."是等效的,意思是解释文本中的命令,并执行。咱们常用的source /etc/profile就是让系统从新执行一下/etc/profile配置脚本。bash
3.使用bash子进程运行cookie
上面使用source和"."来执行脚本,echo $$显示的是当前bash的进程号。
先使用pstree工具查看进程树:
[root@centos-clone1 ~]# pstree
-bash: pstree: command not found
发现Mini版的CentOS默认没有安装pstree,使用yum安装:
yum install psmisc -y
安装完毕后执行pstree:
[root@centos-clone1 ~]# pstree systemd鈹€鈹攢agetty 鈹溾攢auditd鈹€鈹€鈹€{auditd} 鈹溾攢chronyd 鈹溾攢crond 鈹溾攢dbus-daemon 鈹溾攢irqbalance 鈹溾攢lvmetad 鈹溾攢master鈹€鈹攢pickup 鈹 鈹斺攢qmgr 鈹溾攢nginx鈹€鈹€鈹€nginx 鈹溾攢polkitd鈹€鈹€鈹€6*[{polkitd}] 鈹溾攢rsyslogd鈹€鈹€鈹€2*[{rsyslogd}] 鈹溾攢sshd鈹€鈹€鈹€sshd鈹€鈹攢bash鈹€鈹€鈹€pstree 鈹 鈹斺攢bash 鈹溾攢systemd-journal 鈹溾攢systemd-logind 鈹溾攢systemd-udevd 鈹斺攢tuned鈹€鈹€鈹€4*[{tuned}]
发现显示乱码,解决他:
vi /etc/sysconfig/i18n
写入下面内容:
LANG="en_US" SUPPORTED="en_US:en" SYSFONT="latarcyrheb-sun16"
使其生效(临时生效,从新登陆会失效):
source /etc/sysconfig/i18n
而后再次执行pstree:
[root@centos-clone1 ~]# pstree systemd-+-agetty |-auditd---{auditd} |-chronyd |-crond |-dbus-daemon |-irqbalance |-lvmetad |-master-+-pickup | `-qmgr |-nginx---nginx |-polkitd---6*[{polkitd}] |-rsyslogd---2*[{rsyslogd}] |-sshd-+-sshd---bash | `-sshd---bash---pstree |-systemd-journal |-systemd-logind |-systemd-udevd `-tuned---4*[{tuned}]
红色部分显示咱们的pstree运行在一个bash下。
咱们使用命令建立一个子程序bash,并再次使用pstree查看:
[root@centos-clone1 ~]# /bin/bash [root@centos-clone1 ~]# pstree systemd-+-agetty |-auditd---{auditd} |-chronyd |-crond |-dbus-daemon |-irqbalance |-lvmetad |-master-+-pickup | `-qmgr |-nginx---nginx |-polkitd---6*[{polkitd}] |-rsyslogd---2*[{rsyslogd}] |-sshd-+-sshd---bash | `-sshd---bash---bash---pstree |-systemd-journal |-systemd-logind |-systemd-udevd `-tuned---4*[{tuned}]
咱们能够看到,此次的pstree实在新建立的bash子进程上运行的。
此时,咱们有两层bash,咱们可使用exit退出一层:
[root@centos-clone1 ~]# exit
exit
若是自己是最底层的bash,若是使用exit,则会logout。
咱们就能够在建立bash子进程的时候就运行脚本:
[root@centos-clone1 ~]# /bin/bash test.txt hello World total 34 lrwxrwxrwx 1 root root 7 Oct 14 20:48 bin -> usr/bin dr-xr-xr-x. 5 root root 4096 Oct 15 12:59 boot drwxr-xr-x 19 root root 3080 Oct 15 13:49 dev drwxr-xr-x. 76 root root 8192 Oct 22 15:21 etc drwxr-xr-x. 5 root root 40 Oct 20 15:57 home lrwxrwxrwx 1 root root 7 Oct 14 20:48 lib -> usr/lib lrwxrwxrwx 1 root root 9 Oct 14 20:48 lib64 -> usr/lib64 drwxr-xr-x. 2 root root 6 Apr 11 2018 media116980
咱们发现子进程的PID号是116980,这是咱们刚刚建立并运行test.txt的子进程号。
可是咱们运行echo $$看看:
[root@centos-clone1 ~]# echo $$ 116262
发现当前的PID并非116980,说明一个问题,使用/bin/bash来建立子进程运行脚本,在运行完毕后,会结束子进程,回到父进程。
并且每次使用bash来运行脚本,运行时的子进程PID均可能是不同的。
4.bash脚本
咱们在前面看到,使用/bin/bash运行一个命令文本,咱们每次运行时都须要使用/bin/bash test.txt来运行。
咱们能够采用如下形式,将该文本默认使用/bin/bash执行,将文本内容修改成:
#!/bin/bash echo "hello world" ls -l / echo $$
咱们在前面加了一行"#!/bin/bash",指定该文本运行的解释器。这样直接运行文件就能够执行了:
# 将文本的权限修改成可执行 chmod u+x test.txt #运行 ./test.txt
[root@centos-clone1 ~]# ./test.txt hello World total 34 lrwxrwxrwx 1 root root 7 Oct 14 20:48 bin -> usr/bin dr-xr-xr-x. 5 root root 4096 Oct 15 12:59 boot drwxr-xr-x 19 root root 3080 Oct 15 13:49 dev drwxr-xr-x. 76 root root 8192 Oct 22 15:21 etc drwxr-xr-x. 5 root root 40 Oct 20 15:57 home lrwxrwxrwx 1 root root 7 Oct 14 20:48 lib -> usr/lib lrwxrwxrwx 1 root root 9 Oct 14 20:48 lib64 -> usr/lib64 ... 119996
同理,python脚本也是同样的,在开头写上"#!/usr/bin/python"等python命令的path。
在这种脚本的执行方式下,实际上和/bin/bash test.txt同样,是建立了一个子进程来执行,执行完毕后,退出子进程回到父进程。
5.一个重要的问题:为何要在子进程中去执行脚本(重要)
若是咱们的执行脚本有问题,可能致使进程崩溃,那么若是在当前进程执行的话,可能致使bash崩溃。
而咱们若是每次执行脚本都fork出一个子进程执行,那么就算脚本有问题,崩溃的也是子进程,退出后回到父进程,还能够正常运行。
2、脚本中的函数
shell脚本也支持函数。定义好的函数就至关于一个命令。
一个命令只有如下三种状况:
1.在交互命令行下定义一个函数
[root@centos-clone1 ~]# ooxx(){ > echo "Hello World" > ls -l / > echo $$ > }
ooxx是函数名,后面跟一个小括号,不用写参数,只是一个形式。而后接大括号,中间每一行都是一个命令。
运行函数:
[root@centos-clone1 ~]# ooxx Hello World total 34 lrwxrwxrwx 1 root root 7 Oct 14 20:48 bin -> usr/bin dr-xr-xr-x. 5 root root 4096 Oct 15 12:59 boot drwxr-xr-x 19 root root 3080 Oct 15 13:49 dev drwxr-xr-x. 76 root root 8192 Oct 22 15:21 etc drwxr-xr-x. 5 root root 40 Oct 20 15:57 home lrwxrwxrwx 1 root root 7 Oct 14 20:48 lib -> usr/lib lrwxrwxrwx 1 root root 9 Oct 14 20:48 lib64 -> usr/lib64 ...... 116262
直接使用函数名运行,定义好的函数至关于一个命令。
查看该函数的信息:
[root@centos-clone1 ~]# type ooxx ooxx is a function ooxx () { echo "Hello World"; ls --color=auto -l /; echo $$ }
能够看到ooxx是一个方法,并给出具体方法体。
3、重定向
首先重定向不是命令!!
每一个程序都有I/O:
1.示例,查看程序I/O位置
Linux一切皆文件,I/O也是抽象成文件的。
首先,咱们来查看一下bash的I/O。
查看当前bash的进程号:
[root@centos-clone1 ~]# echo $$ 1594
查看bash进程的I/O文件:
[root@centos-clone1 fd]# cd /proc/$$/fd [root@centos-clone1 fd]# ll total 0 lrwx------ 1 root root 64 Oct 24 01:05 0 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 01:05 1 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 01:05 2 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 22:25 255 -> /dev/pts/0
$$表示当前bash的进程PID,为1594。在系统运行的时候,全部程序都会在/proc中抽象出一个文件夹,里面存放的是程序的运行数据。
在/proc中找到名为1594的文件夹,这个文件夹就是bash的运行数据,里面有一个叫fd的文件夹(fd表明文件描述符),其中的0、一、2就是他的标准输入、标准输出】错误输出。
这两个I/O都是默认指向/dev/pts/0的,也就是命令行终端。也就是为何咱们使用命令的时候,结果会显示在当前终端屏幕上的缘由。
2.开启第2个终端
当咱们使用ssh连接第二个终端(多用户登陆)时,新的用户也会启动一个新的bash程序:
[root@centos-clone1 ~]# echo $$ 63926
查看新终端bash的I/O文件:
[root@centos-clone1 ~]# cd /proc/63926/fd [root@centos-clone1 fd]# ll total 0 lrwx------ 1 root root 64 Oct 24 22:29 0 -> /dev/pts/1 lrwx------ 1 root root 64 Oct 24 22:29 1 -> /dev/pts/1 lrwx------ 1 root root 64 Oct 24 22:29 2 -> /dev/pts/1 lrwx------ 1 root root 64 Oct 24 22:30 255 -> /dev/pts/1
咱们发现他的标准输入输出都是指向/dev/pts/1的,说明每一个终端都会对应一个输入输出文件(对应终端)。
咱们在设备信息中能够看到这两个终端的I/O设备:
[root@centos-clone1 pts]# cd /dev/pts/ [root@centos-clone1 pts]# ll total 0 crw--w---- 1 root tty 136, 0 Oct 24 22:25 0 crw--w---- 1 root tty 136, 1 Oct 24 2019 1 c--------- 1 root root 5, 2 Oct 24 01:02 ptmx
在/dev/pts中,咱们看到0、1两个I/O设备,对应的就是咱们启动的两个终端,对应两个bash进程。
3.初试重定向
咱们能够将1号终端的标准输出重定向到2号终端,达到的效果是,1号终端运行"ls -l /"命令,返回的结果在2号终端显示。
首先,咱们备份1号终端的标准输出,以便于恢复:
[root@centos-clone1 fd]# cd /proc/$$/fd [root@centos-clone1 fd]# ll total 0 lrwx------ 1 root root 64 Oct 24 01:05 0 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 01:05 1 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 01:05 2 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 22:25 255 -> /dev/pts/0 [root@centos-clone1 fd]# exec 6>&1 [root@centos-clone1 fd]# ll total 0 lrwx------ 1 root root 64 Oct 24 01:05 0 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 01:05 1 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 01:05 2 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 22:25 255 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 01:05 6 -> /dev/pts/0
而后,将标准输出1,指向/dev/pts/1文件:
[root@centos-clone1 fd]# exec 1> /dev/pts/1
此时,终端1的标准输出已经重定向到终端2的输入设备:
# 终端1中执行ls -l [root@centos-clone1 fd]# ls -l /usr [root@centos-clone1 fd]#
终端1中什么都没有显示,观察终端2:
# 终端2中显示了/usr中全部的内容列表
total 128 dr-xr-xr-x. 2 root root 20480 Oct 23 21:20 bin drwxr-xr-x. 2 root root 6 Apr 11 2018 etc drwxr-xr-x. 2 root root 6 Apr 11 2018 games drwxr-xr-x. 42 root root 8192 Oct 21 16:03 include drwxr-xr-x 3 root root 51 Oct 22 14:56 java dr-xr-xr-x. 27 root root 4096 Oct 21 16:00 lib dr-xr-xr-x. 38 root root 20480 Oct 21 16:03 lib64 drwxr-xr-x. 22 root root 4096 Oct 21 16:00 libexec drwxr-xr-x. 13 root root 4096 Oct 21 23:35 local dr-xr-xr-x. 2 root root 16384 Oct 23 21:20 sbin drwxr-xr-x. 76 root root 4096 Oct 21 16:03 share drwxr-xr-x. 4 root root 32 Apr 11 2018 src lrwxrwxrwx 1 root root 10 Oct 14 20:48 tmp -> ../var/tmp
实现完毕后,将终端1的标准输出流恢复回去:
[root@centos-clone1 fd]# exec 1>&6 [root@centos-clone1 fd]# ll total 0 lrwx------ 1 root root 64 Oct 24 01:05 0 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 01:05 1 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 01:05 2 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 22:25 255 -> /dev/pts/0 lrwx------ 1 root root 64 Oct 24 01:05 6 -> /dev/pts/0
4.重定向输出到文件
将ls -l /usr的输出重定向到文件中:
[root@centos-clone1 ~]# ls -l /usr 1> ~/usr_list [root@centos-clone1 ~]# cat usr_list
total 128 dr-xr-xr-x. 2 root root 20480 Oct 23 21:20 bin drwxr-xr-x. 2 root root 6 Apr 11 2018 etc drwxr-xr-x. 2 root root 6 Apr 11 2018 games drwxr-xr-x. 42 root root 8192 Oct 21 16:03 include drwxr-xr-x 3 root root 51 Oct 22 14:56 java dr-xr-xr-x. 27 root root 4096 Oct 21 16:00 lib dr-xr-xr-x. 38 root root 20480 Oct 21 16:03 lib64 drwxr-xr-x. 22 root root 4096 Oct 21 16:00 libexec drwxr-xr-x. 13 root root 4096 Oct 21 23:35 local dr-xr-xr-x. 2 root root 16384 Oct 23 21:20 sbin drwxr-xr-x. 76 root root 4096 Oct 21 16:03 share drwxr-xr-x. 4 root root 32 Apr 11 2018 src lrwxrwxrwx 1 root root 10 Oct 14 20:48 tmp -> ../var/tmp
若是进行第2次命令,也重定向到该文件,则内容会被覆盖:
[root@centos-clone1 ~]# ls -l / 1> ~/usr_list [root@centos-clone1 ~]# cat usr_list total 32 lrwxrwxrwx 1 root root 7 Oct 14 20:48 bin -> usr/bin dr-xr-xr-x. 5 root root 4096 Oct 15 12:59 boot drwxr-xr-x 19 root root 3080 Oct 24 01:02 dev drwxr-xr-x. 76 root root 8192 Oct 24 01:02 etc drwxr-xr-x. 5 root root 40 Oct 20 15:57 home lrwxrwxrwx 1 root root 7 Oct 14 20:48 lib -> usr/lib lrwxrwxrwx 1 root root 9 Oct 14 20:48 lib64 -> usr/lib64 drwxr-xr-x. 2 root root 6 Apr 11 2018 media drwxr-xr-x. 2 root root 6 Apr 11 2018 mnt drwxr-xr-x. 2 root root 6 Apr 11 2018 opt dr-xr-xr-x 115 root root 0 Oct 24 01:02 proc dr-xr-x---. 8 root root 4096 Oct 24 22:42 root drwxr-xr-x 23 root root 640 Oct 24 01:02 run lrwxrwxrwx 1 root root 8 Oct 14 20:48 sbin -> usr/sbin drwxrwx--- 2 root leoshare 18 Oct 20 16:20 share drwxr-xr-x. 2 root root 6 Apr 11 2018 srv dr-xr-xr-x 13 root root 0 Oct 24 01:02 sys drwxrwxrwt. 9 root root 4096 Oct 24 03:44 tmp drwxr-xr-x. 14 root root 4096 Oct 22 14:55 usr drwxr-xr-x. 19 root root 4096 Oct 14 20:48 var
因此">"符号叫作覆盖重定向。
若是想要追加,则使用">>"符号:
[root@centos-clone1 ~]# ls -l / 1>> ~/usr_list
5.错误输出
每一个程序除了有标准输入和标准输出,还有错误输出,也就是2指向的文件。
在Bash的错误输出默认是输出到终端的,也就是/dev/pts/下的0、1等设备。
#god目录不存在的状况 [root@centos-clone1 ~]# ls -l /god ls: cannot access /god: No such file or directory
此时返回的错误信息就是错误输出。
将错误输出重定向到文件(使用"2>"符号):
[root@centos-clone1 ~]# ls -l /god 2> err.log [root@centos-clone1 ~]# cat err.log ls: cannot access /god: No such file or directory
6.重定向的绑定顺序
示例:
# /god目录不存在,/usr目录存在 ls -l /god /usr 2>&1 1> a.out
[root@centos-clone1 ~]# ls -l /god /usr 2>&1 1> a.out ls: cannot access /god: No such file or directory [root@centos-clone1 ~]# cat a.out /usr: total 128 40 dr-xr-xr-x. 2 root root 20480 Oct 23 21:20 bin 0 drwxr-xr-x. 2 root root 6 Apr 11 2018 etc 0 drwxr-xr-x. 2 root root 6 Apr 11 2018 games 12 drwxr-xr-x. 42 root root 8192 Oct 21 16:03 include 0 drwxr-xr-x 3 root root 51 Oct 22 14:56 java 4 dr-xr-xr-x. 27 root root 4096 Oct 21 16:00 lib 40 dr-xr-xr-x. 38 root root 20480 Oct 21 16:03 lib64 4 drwxr-xr-x. 22 root root 4096 Oct 21 16:00 libexec 4 drwxr-xr-x. 13 root root 4096 Oct 21 23:35 local 20 dr-xr-xr-x. 2 root root 16384 Oct 23 21:20 sbin 4 drwxr-xr-x. 76 root root 4096 Oct 21 16:03 share 0 drwxr-xr-x. 4 root root 32 Apr 11 2018 src 0 lrwxrwxrwx 1 root root 10 Oct 14 20:48 tmp -> ../var/tmp
上述运行结果中,/usr的内容列表被保存在了a.out中,而ls -l /god的错误信息打印在了屏幕上。
说明错误输出流没有绑定生效,咱们将2个绑定换一下位置:
ls -l /god /usr 1> a.out 2>&1
[root@centos-clone1 ~]# ls -l /god /usr 1> a.out 2>&1 [root@centos-clone1 ~]# cat a.out ls: cannot access /god: No such file or directory /usr: total 128 40 dr-xr-xr-x. 2 root root 20480 Oct 23 21:20 bin 0 drwxr-xr-x. 2 root root 6 Apr 11 2018 etc 0 drwxr-xr-x. 2 root root 6 Apr 11 2018 games 12 drwxr-xr-x. 42 root root 8192 Oct 21 16:03 include 0 drwxr-xr-x 3 root root 51 Oct 22 14:56 java 4 dr-xr-xr-x. 27 root root 4096 Oct 21 16:00 lib 40 dr-xr-xr-x. 38 root root 20480 Oct 21 16:03 lib64 4 drwxr-xr-x. 22 root root 4096 Oct 21 16:00 libexec 4 drwxr-xr-x. 13 root root 4096 Oct 21 23:35 local 20 dr-xr-xr-x. 2 root root 16384 Oct 23 21:20 sbin 4 drwxr-xr-x. 76 root root 4096 Oct 21 16:03 share 0 drwxr-xr-x. 4 root root 32 Apr 11 2018 src 0 lrwxrwxrwx 1 root root 10 Oct 14 20:48 tmp -> ../var/tmp
此时,咱们发现标准输出和错误输出都输出到了a.out。
以上实验说明重定向绑定的顺序是从左到右的。
标准输出和错误输出定位到一个文件的特殊写法:
# 二者同样的效果,都是将一、2都定向到a.out [root@centos-clone1 ~]# ls -l /god /usr >& a.out [root@centos-clone1 ~]# ls -l /god /usr &> a.out
7.输入重定向
输入重定向有三种方式:
read命令:接收标准输入的字符串,以换行符结果,相似python的input:
[root@centos-clone1 ~]# read var1 123abcd [root@centos-clone1 ~]# echo $var1 123abcd
咱们能够用如下形式重定向输入:
[root@centos-clone1 ~]# read var1 0<<<"abcd123" [root@centos-clone1 ~]# echo $var1 abcd123
使用三个"<"表示将一行字符串重定向到输入。
重定向一大段文本到输入:
[root@centos-clone1 ~]# read var2 0<<ooxx > abc > 123 > uuiery > sdfj > ooxx [root@centos-clone1 ~]# echo $var2 abc
使用两个"<"能够将多行文本重定向到输入,以ooxx做为整段文本的结束字符。
可是咱们在打印$var2时,发现只有abc三个字符,这是由于read命令对换行符敏感,在输入流中确实有咱们输入的全部字符,可是read只读到了第一行。
此时,咱们将read换为cat,就正确了:
[root@centos-clone1 ~]# cat 0<<ooxx > abc > 123 > skdfjksf > ooxx abc 123 skdfjksf
咱们能够看到,cat正确读到了除了ooxx(咱们定义的结束符)之外的全部行内容。
将文件重定向到标准输入:
[root@centos-clone1 ~]# cat 0< myname.txt My name is Leo...
这里只是一个示例,没有实际应用价值,由于上述命令等效于cat myname.txt。
8.一个综合的重定向示例(经过重定向请求www.baidu.com首页)
咱们使用exec定义一个TCP socket:
# 将描述符8指向tcp socket,与baidu创建链接 [root@centos-clone1 fd]# exec 8<> /dev/tcp/www.baidu.com/80 # 向8发送http请求头 [root@centos-clone1 fd]# echo -e "GET / HTTP/1.0\n" 1>&8 # 从8接收http响应 [root@centos-clone1 fd]# cat 0<&8
咱们发现,www.baidu.com返回的响应打印在屏幕上:
[root@centos-clone1 fd]# cat 0<&8 HTTP/1.0 200 OK Accept-Ranges: bytes Cache-Control: no-cache Content-Length: 14615 Content-Type: text/html Date: Fri, 25 Oct 2019 06:38:48 GMT P3p: CP=" OTI DSP COR IVA OUR IND COM " P3p: CP=" OTI DSP COR IVA OUR IND COM " Pragma: no-cache Server: BWS/1.1 Set-Cookie: BAIDUID=160ACF42CC151F0ED2B6D3241C0FE02A:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie: BIDUPSID=160ACF42CC151F0ED2B6D3241C0FE02A; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie: PSTM=1571985528; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com Set-Cookie: BAIDUID=160ACF42CC151F0EA49EFE62D0FE9DB0:FG=1; max-age=31536000; expires=Sat, 24-Oct-20 06:38:48 GMT; domain=.baidu.com; path=/; version=1; comment=bd Traceid: 157198552802713482341501073886667258581 Vary: Accept-Encoding X-Ua-Compatible: IE=Edge,chrome=1 ...... ...... ...... http:\/\/.+\.baidu\.com'))[0];}}name && ns_c({'fm': 'behs','tab': name,'query': encodeURIComponent(key),'un': encodeURIComponent(bds.comm.user || '') });};}})();};if(window.pageState==0){initIndex();}})();document.cookie = 'IS_STATIC=1;expires=' + new Date(new Date().getTime() + 10*60*1000).toGMTString();</script> </body></html>
若是咱们想存入文件,在使用cat读取响应的时候,将输出重定向到文件就能够了:
[root@centos-clone1 fd]# exec 8<> /dev/tcp/www.baidu.com/80 [root@centos-clone1 fd]# echo -e "GET / HTTP/1.0\n" 1>&8 [root@centos-clone1 fd]# cat 0<&8 1> ~/baidu.txt [root@centos-clone1 fd]# cat ~/baidu.txt
注意:若是在建立了socket后,咱们过一段时间才发送请求,可能由于socket连接超时而收不到响应,此时应该从新绑定8>/dev/tcp/www..baidu.com/80。
2、变量
1.变量生命周期
变量的生命周期是在当前运行bash的进程中的,当前bash退出后,变量也就消亡了。
[root@centos-clone1 ~]# myname=Leo [root@centos-clone1 ~]# echo $myname Leo [root@centos-clone1 ~]# /bin/bash [root@centos-clone1 ~]# echo $myname [root@centos-clone1 ~]# exit exit [root@centos-clone1 ~]# echo $myname Leo
咱们能够看到,定义一个变量myname为Leo,当前进程中打印$myname输出Leo正确。
咱们启动一个bash子进程,并在子进程中打印$myname,输出空,表示没有这个变量。
退出子进程回到父进程,再次打印$myname,正确显示Leo。
2.局部变量
在函数内部,咱们能够定义局部变量:
[root@centos-clone1 ~]# ooxx(){ > local age=32 > echo $age > } [root@centos-clone1 ~]# ooxx 32 [root@centos-clone1 ~]# echo $age
#输出空
能够看到在函数内部定义的局部变量的生命周期只是在函数内部,函数执行完后,局部变量消亡。
3.函数内部访问普通变量
[root@centos-clone1 ~]# ooxx(){ > echo $myname > myname=John > echo $myname > } [root@centos-clone1 ~]# ooxx Leo John [root@centos-clone1 ~]# echo $myname John
能够看到,函数内部能够访问普通变量,并能够修改其值。
4.参数
对于shell脚本和shell函数,咱们都须要传入参数。
和JAVA,C++等高级语言不一样,shell的参数不须要形参,而是经过一些符号来取值:
[root@centos-clone1 ~]# ooxx(){ > echo $1 > echo $2 > echo $# > } [root@centos-clone1 ~]# ooxx 1 2 3 4 5 1 2 5
咱们能够看到,shell中的参数是使用$一、$二、$#、$*等来取值的,以下表所示:
# 参数个数 $# # 参数列表 $* # 参数列表 $@ # 第一个参数 $1 # 第二个参数 $2 # 第11个参数 ${11}
使用示例:
# 建立一个脚本 vi test.sh
# 输入内容 echo $1 echo $2 echo $11 echo ${11} echo $# echo $* echo $@
运行脚本:
[root@centos-clone1 ~]# . test.sh 1 2 3 4 5 6 7 8 9 0 a b c 1 2 11 a 13 1 2 3 4 5 6 7 8 9 0 a b c 1 2 3 4 5 6 7 8 9 0 a b c
咱们输入了13个参数
$1为1正确。$2为2正确。$11为11,不正确,是先取了$1而后拼接上1,获得11。${11}取第11个参数为a是正确的。
$#输出13正确,一共有13个参数。$*和$@输出参数列表。
5.管道
重要:管道符号"|"实际上会开启多个子进程来完成先后的命令。
[root@centos-clone1 ~]# ls -l / | more
也就是"|"左边的ls命令和右边的more命令,会分别新建一个子进程来分别执行。管道打通两个子进程之间的通道,将ls的输出传递给more命令。
咱们验证一下:
[root@centos-clone1 ~]# echo $$ 1594 [root@centos-clone1 ~]# echo $$ | more 1594
第一个echo $$打印的是当前进程的PID,而根据上述的说法,第二个命令打印的PID不该该是1594。这里出现了问题:
咱们将echo $$换一个命令:
[root@centos-clone1 ~]# echo $BASHPID 1594 [root@centos-clone1 ~]# echo $BASHPID | more 113173
使用echo $BASHPID一样获取的是进程的PID。可是第二个命令的结果却真正输出了子进程的PID。
这是由于,echo $$的优先级高于管道符号"|":
# bash先看到echo $$并将其执行,结果替换为了1594,而后再启动子进程 echo $$ | more # 至关于 echo 1594 | more
而echo $BASHPID只是一个普通取值,优先级低于管道符号"|":
# 先启动了子进程,而后在子进程中执行echo $BASHPID echo $BASHPID | more
# 因此$BASHPID拿到的是子进程的PID
6.判断一个命令是否执行成功
[root@centos-clone1 ~]# ls -l / total 32 lrwxrwxrwx 1 root root 7 Oct 14 20:48 bin -> usr/bin dr-xr-xr-x. 5 root root 4096 Oct 15 12:59 boot drwxr-xr-x 19 root root 3080 Oct 24 01:02 dev drwxr-xr-x. 76 root root 8192 Oct 24 01:02 etc drwxr-xr-x. 5 root root 40 Oct 20 15:57 home lrwxrwxrwx 1 root root 7 Oct 14 20:48 lib -> usr/lib lrwxrwxrwx 1 root root 9 Oct 14 20:48 lib64 -> usr/lib64 drwxr-xr-x. 2 root root 6 Apr 11 2018 media drwxr-xr-x. 2 root root 6 Apr 11 2018 mnt drwxr-xr-x. 2 root root 6 Apr 11 2018 opt dr-xr-xr-x 114 root root 0 Oct 24 01:02 proc dr-xr-x---. 8 root root 4096 Oct 25 15:11 root drwxr-xr-x 23 root root 640 Oct 24 01:02 run lrwxrwxrwx 1 root root 8 Oct 14 20:48 sbin -> usr/sbin drwxrwx--- 2 root leoshare 18 Oct 20 16:20 share drwxr-xr-x. 2 root root 6 Apr 11 2018 srv dr-xr-xr-x 13 root root 0 Oct 24 22:44 sys drwxrwxrwt. 9 root root 4096 Oct 25 14:30 tmp drwxr-xr-x. 14 root root 4096 Oct 22 14:55 usr drwxr-xr-x. 19 root root 4096 Oct 14 20:48 var [root@centos-clone1 ~]# echo $? 0 [root@centos-clone1 ~]# ls -l /god ls: cannot access /god: No such file or directory [root@centos-clone1 ~]# echo $? 2
咱们能够看到,当ls执行成功时,$?的值为0。
当ls执行失败时,$?的值为非0。
因此,咱们在脚本中能够经过判断$?来判断前面的命令是否执行成功。
3、环境变量
前面咱们提到的变量的生命周期都是限于当前进程的。
咱们的父进程要建立一个子进程,而子进程也要获取到某个变量的值(环境变量)。
什么叫作环境变量,环境多是包含多个进程的一个环境,则环境变量可以供多个进程访问。
[root@centos-clone1 ~]# echo $env_var 999 [root@centos-clone1 ~]# /bin/bash [root@centos-clone1 ~]# echo $env_var 999
使用export将变量导出,则能够供子进程访问。
注意:这里的export是导出而不是共享,也就是说该变量可供该父进程建立的全部子进程访问,可是不是共享的。当子进程修改该变量时,父进程中这个变量的值不发生改变。反之同理。
重要:export导出的变量,至关于在子进程中有一个拷贝,各自拥有一个独立的副本。
在这种原理下,咱们考虑一个场景:
当父进程中有大量的导出的环境变量,每一个变量的值占用空间很大。全部环境变量加起来例若有10GB,那么父进程建立子进程时,理论上会将全部的环境变量进行拷贝。子进程建立速度会很是缓慢。
操做系统内核设计者如何解决这个问题,答案时使用copy on write技术。
即,建立子进程时,全部的环境变量以引用的方式传递给子进程,当父进程要修改某个环境变量时,在修改以前,将副本拷贝给子进程,这样将10G的变量分时拷贝,就能够解决这个问题。
一样的,当子进程要修改某个变量时,则先拷贝该变量,而后再进行修改。
4、其余一些知识点
1.引用
双引号引用:
[root@centos-clone1 ~]# name=Leo [root@centos-clone1 ~]# echo $name Leo [root@centos-clone1 ~]# echo "$name say hello" Leo say hello
双引号能够拼接中间带空格的字符串。而且其中的$name优先级更好,bash会取到name的值。
单引号引用:
[root@centos-clone1 ~]# name=Leo [root@centos-clone1 ~]# echo $name Leo [root@centos-clone1 ~]# echo '$name say hello' $name say hello
咱们发现单引号中的$name没法取值,说明$取值的优先级低于单引号。
2.将命令输出赋值给变量
[root@centos-clone1 ~]# lines=`ls -l / | wc -l` [root@centos-clone1 ~]# echo $lines 21
整个命令用反引号引发来。
5、表达式
1.算术表达式
第一种形式:let c=$a+$b
[root@centos-clone1 ~]# a=1 [root@centos-clone1 ~]# b=2 [root@centos-clone1 ~]# let c=$a+$b [root@centos-clone1 ~]# echo $c 3
第二种形式:c=$((a+b))
[root@centos-clone1 ~]# c=$((a+b)) [root@centos-clone1 ~]# echo $c 3
2.条件表达式(逻辑表达式)
使用test作条件判断,查看help test:
[root@centos-clone1 ~]# help test test: test [expr] Evaluate conditional expression. File operators: -a FILE True if file exists. -b FILE True if file is block special. -c FILE True if file is character special. -d FILE True if file is a directory. -e FILE True if file exists. -f FILE True if file exists and is a regular file. -g FILE True if file is set-group-id. -h FILE True if file is a symbolic link. -L FILE True if file is a symbolic link. -k FILE True if file has its `sticky' bit set. -p FILE True if file is a named pipe. -r FILE True if file is readable by you. -s FILE True if file exists and is not empty. -S FILE True if file is a socket. -t FD True if FD is opened on a terminal. -u FILE True if the file is set-user-id. -w FILE True if the file is writable by you. -x FILE True if the file is executable by you. -O FILE True if the file is effectively owned by you. -G FILE True if the file is effectively owned by your group. -N FILE True if the file has been modified since it was last read. FILE1 -nt FILE2 True if file1 is newer than file2 (according to modification date). FILE1 -ot FILE2 True if file1 is older than file2. FILE1 -ef FILE2 True if file1 is a hard link to file2. String operators: -z STRING True if string is empty. -n STRING STRING True if string is not empty. STRING1 = STRING2 True if the strings are equal. STRING1 != STRING2 True if the strings are not equal. STRING1 < STRING2 True if STRING1 sorts before STRING2 lexicographically. STRING1 > STRING2 True if STRING1 sorts after STRING2 lexicographically. Other operators: -o OPTION True if the shell option OPTION is enabled. -v VAR True if the shell variable VAR is set ! EXPR True if expr is false. EXPR1 -a EXPR2 True if both expr1 AND expr2 are true. EXPR1 -o EXPR2 True if either expr1 OR expr2 is true. arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne, -lt, -le, -gt, or -ge.
能够从帮助中看到,test能够作文件状态的判断,文件之间的比较,以及字符串的比较,数值的比较等等。
使用test比较两个数字:
[root@centos-clone1 ~]# test 3 -gt 8 [root@centos-clone1 ~]# echo $? 1
3大于8,结果应该是假,因此$?的值为非0。
[root@centos-clone1 ~]# test 3 -lt 8 [root@centos-clone1 ~]# echo $? 0
反之,命令的执行状态为0,表示结果为真。
因为运行的结果是以命令成败与否来判断,则通常使用如下形式:
[root@centos-clone1 ~]# test 3 -gt 8 && echo "It's true"
当前面成功运行时,后面的才会执行。
也能够写成更清晰的形式:
[root@centos-clone1 ~]# [ 3 -gt 8 ] && echo "It's true"
咱们看一下"[]"的信息:
[root@centos-clone1 ~]# type '[' [ is a shell builtin
发现[]是一个bash内建命令,是命令后面就必须跟一个空格。。。
[root@centos-clone1 ~]# [3 -gt 8 ] && echo "It's true" bash: [3: command not found [root@centos-clone1 ~]# [ 3 -gt 8] && echo "It's true" bash: [: missing `]'
前中括号的后面以及后中括号的前面都必要带空格,不然会报错。
总结:只要是命令,后面必须有空格。
若是要取反:
[root@centos-clone1 ~]# [ ! 3 -gt 8 ] && echo "It's true"
取反就在前面加"!"。
6、示例(编写一个自动建立用户的脚本)
#!/bin/bash [ $# -eq 2 ] || exit 2 adduser $1 echo $2 | passwd --stdin $1 &> /dev/null echo "Add User Seccuss..."
解释:
1)[ $# -eq 2 ] || exit 2,表示判断参数是不是2个,若是不是2个,则退出脚本。exit后面跟数字标准脚本的执行状态,0表示正确退出,1-127表示错误。
2) 建立用户,用户名从第一个参数获取
3) 设置用户密码,从第二个参数获取,注意这里passwd命令要接受标准输入必须使用--stdin选项。并将命令的返回信息所有扔掉,/dev/null是一个数据黑洞。
4) 打印一个消息,表示添加成功。
添加用户是否存在的验证:
[root@centos-clone1 ~]# id leokale uid=1003(leokale) gid=1004(leokale) groups=1004(leokale) [root@centos-clone1 ~]# echo $? 0
当用户存在时id命令执行正确,返回0。
[root@centos-clone1 ~]# id leokale id: leokale: no such user [root@centos-clone1 ~]# echo $? 1
当用户不存在时,id命令执行失败,返回非0数字1。
因此,咱们能够经过id命令的执行状态来判断该用户是否存在:
#!/bin/bash [ ! $# -eq 2 ] && echo "number of args wrong.." && exit 2 id $1 &> /dev/null && echo "User is exist..." && exit 5 adduser $1 echo $2 | passwd --stdin $1 &> /dev/null echo "Add User Seccuss..."
经过id命令判断指定建立的用户是否存在,若是存在,则提示用户存在,而后错误退出,退出码为5。若是用户不存在,则执行后面的逻辑,建立指定用户。
只有root用户才有权限添加用户,咱们要考虑普通用户误运行该脚本的状况:
#!/bin/bash [ ! $# -eq 2 ] && echo "number of args wrong.." && exit 2 id $1 &> /dev/null && echo "User is exist..." && exit 5 adduser $1 &>/dev/null && echo $2 | passwd --stdin $1 &> /dev/null && echo "Add User Seccuss..." $$ exit 0 echo "Somewhere wrong.." exit 7
咱们将建立用户,建立密码都用&&串起来,这样当建立用户失败的状况下,横向命令都不会继续执行,而后错误退出,退出码为7。
# 使用普通用户leo来执行脚本
[leo@centos-clone1 tmp]$ ./addUser number of args wrong.. [leo@centos-clone1 tmp]$ ./addUser leokale 52myself User is exist... [leo@centos-clone1 tmp]$ ./addUser sjdjkf sdf Somewhere wrong..
7、控制语句
1.if判断语句
查看if的帮助:
[leo@centos-clone1 tmp]$ help if if: if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi Execute commands based on conditional. ...... ...... Exit Status: Returns the status of the last command executed.
咱们能够看到if语句的基本形式为:if 命令; then 命令; else 命令; fi。。以if开始,以fi结尾。
if语句使用示例:
[root@centos-clone1 ~]# vi test2.sh if ls -l / &> /dev/null; then echo "OK!"; else echo "Not OK!"; fi
[root@centos-clone1 ~]# vi test2.sh if [ 3 -gt 8 ]; then echo "OK!"; else echo "Not OK!"; fi
分行写法:
[root@centos-clone1 ~]# vi test2.sh if ls -l / &> /dev/null; then
echo "OK!" else
echo "Not OK!" fi
2.for循环
查看for的帮助:
[root@centos-clone1 ~]# help for for: for NAME [in WORDS ... ] ; do COMMANDS; done Execute commands for each member in a list. ...... for ((: for (( exp1; exp2; exp3 )); do COMMANDS; done Arithmetic for loop. Equivalent to (( EXP1 )) while (( EXP2 )); do COMMANDS (( EXP3 )) done ......
for循环使用示例:
[root@centos-clone1 ~]# vi test3.sh for((i=0;i<10;i++));do echo $i;done
[root@centos-clone1 ~]# chmod +x test3.sh [root@centos-clone1 ~]# ./test3.sh 0 1 2 3 4 5 6 7 8 9
分行写:
[root@centos-clone1 ~]# vi test3.sh for((i=0;i<10;i++)); do
echo $i done
加强for循环:
[root@centos-clone1 ~]# vi test4.sh for name in leo john "leo kale";do echo $name;done
[root@centos-clone1 ~]# ./test4.sh leo john leo kale
分行写法:
[root@centos-clone1 ~]# vi test4.sh for name in leo john "leo kale";do echo $name done
3.while循环
[root@centos-clone1 ~]# vi test5.sh while ls -l /god;do echo "ok" rm -rf /god done
[root@centos-clone1 ~]# ./test5.sh total 0 ok ls: cannot access /god: No such file or directory
预先建立一个/god目录,而后执行上述脚本,循环第一次发现有/god目录,而后执行删除操做,第二轮循环发现目录不存在,则退出。
4.case多分支
[root@centos-clone1 ~]# vi test6.sh name=$1 case $name in "john") echo "John";; "leo") echo "Leo";; *) echo "wrong";; esac
[root@centos-clone1 ~]# ./test6.sh leo Leo [root@centos-clone1 ~]# ./test6.sh john John [root@centos-clone1 ~]# ./test6.sh leokale wrong
8、示例
加强For循环读取一个文件,打印每一行,并统计行数:
[root@centos-clone1 ~]# vi scanfile.sh #!/bin/bash oldIFS=$IFS IFS=$'\n' num=0 content=`cat testfile.txt` for i in $content;do echo $i ((num++)) done echo "$num lines" IFS=$oldIFS
解释:
1)首先咱们备份IFS,IFS是一个环境变量,其中定义了作文本分割的符号,例如空格、换行等。
2)将IFS赋值为只有换行符,不然文件中的每一行若是存在空格,则会被自动切分红多行。
3)完成后,须要将IFS回复,不影响其余程序的运行。
逐步For循环方式:
[root@centos-clone1 ~]# vi scanfile.sh #!/bin/bash num=0 lines=`cat testfile.txt | wc -l` for((i=1;i<=lines;i++));do head -$i testfile.txt | tail -1 ((num++)) done echo num:$num
解释:
1)使用cat testfile.txt | wc -l获取文件行数
2) 从第1行开始for循环遍历,获取每一行内容,并打印
while循环方式:
[root@centos-clone1 ~]# vi scanfile.sh #!/bin/bash exec 8<$0 exec 0< testfile.txt num=0 while read line;do echo $line ((num++)) done echo num:$num exec 0<&8
解释:
1)先备份准备输入
2)重定向标准输入为testfile.txt文件
3)使用read从输入流中读取一行(read对换行符敏感),循环读取
4)回复标准输入