BashShell基础

基础

image-20210531165416241

登录shell:优先/etc/profile,然后加载~/.bash_profile ,再次加载~/.bashrc,最后加载/etc/bashrc

非登录shell:/etc/bashrc,~/.bashrc在执行一个非登录shell时调用

 

变量

运算符号

运算方法

用户交互

条件测试

image-20210531170558748

 

vim配置

流程控制语句语法

 

 

 

 

 

 

 

select

 

 

array

 

 

 

 

练习案例

快速使用记录

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BashShell高级

shell脚本实现并发执行

文件描述符

在linux下fd叫做文件描述符,在window下fd叫做句柄,所以这就说明了为什么在官方文档中fileno解释是Return the file descriptor or handle used by the connection.

 

在内核中每一个打开的文件都需要由3种数据结构来进行维护

根据文中内容,这三种数据结构分别为:打开文件描述符表、打开文件表、inode节点

1.每个进程对应一张打开文件描述符表,这是进程级数据结构,也就是每一个进程都各自有这样一个数据结构;

2.内核维持一张打开文件表,文件表由多个文件表项组成,这是系统级数据结构,也就是说这样的数据结构是针对于整个内核而言的,每个进程都可共享的;

3.每个打开的文件对应一个i节点(i-node)数据结构(Linux下只有i节点没有v节点),由于这是每一个打开的文件与之对应的,因此这也是一个系统级数据结构,存在于内核中,非进程所独有。

https://www.cnblogs.com/lwyeric/p/13598704.html

https://www.cnblogs.com/alan666/p/8311890.html

 

 

 

 

exec命令

  bash shell的内建命令exec将并不启动新的shell,而是用要被执行命令替换当前的shell进程,并且将老进程的环境清理掉,而且exec命令后的其它命令将不再执行。

  因此,如果你在一个shell里面,执行exec ls;那么,当列出了当前目录后,这个shell就自己退出了,因为这个shell进程已被替换为仅仅执行ls命令的一个进程,执行结束自然也就退出了。为了避免这个影响我们的使用,一般将exec命令放到一个shell脚本里面,用主脚本调用这个脚本,调用点处可以用bash a.sh,(a.sh就是存放该命令的脚本),这样会为a.sh建立一个sub shell去执行,当执行到exec后,该子脚本进程就被替换成了相应的exec的命令。source命令或者”.”,不会为脚本新建shell,而只是将脚本包含的命令在当前shell执行。不过,要注意一个例外,当exec命令来对文件描述符操作的时候,就不会替换shell,而且操作完成后,还会继续执行接下来的命令。

  exec 3<&0:这个命令就是将操作符3也指向标准输入。

  总结:exec 命令:常用来替代当前 shell 并重新启动一个 shell,换句话说,并没有启动子 shell。使用这一命令时任何现有环境都将会被清除。exec 在对文件描述符进行操作的时候,也只有在这时,exec 不会覆盖你当前的 shell 环境。

exec描述
exec ls在shell中执行ls,ls结束后不返回原来的shell中了
exec <filefile中的内容作为标准输入(替代STDIN)
exec >file将标准输出写入file(替代STDOUT)
exec 3<file将file读入到文件描述符3中(此时,创建了文件描述符3)
sort <&3将文件描述符3作为临时输入,用于sort排序
exec 4>file将写入文件描述符4中的内容写入file中(此时,创建了文件描述符4)
ls >&4ls将不会有显示,直接写入文件描述符4中了,即上面的file中
exec 5<&4创建文件描述符4的拷贝文件描述符5
exec 3<&-关闭文件描述符3

&- 关闭标准输出

n&- 表示将 n 号输出关闭

上述所有形式都可以前导一个数字,此时建立的文件描述符由这个数字指定而不是缺省的 0 或 1。如:

2>file 运行一个命令并把错误输出(文件描述符 2)定向到 file。

2>&1 运行一个命令并把它的标准输出和输出合并。(严格的说是通过复制文件描述符 1 来建立文件描述符 2 ,但效果通常是合并了两个流。)

我们对 2>&1详细说明一下 :2>&1 也就是 FD2=FD1 ,这里并不是说FD2 的值 等于FD1的值,因为 > 是改变送出的数据信道,也就是说把 FD2 的 “数据输出通道” 改为 FD1 的 “数据输出通道”。如果仅仅这样,这个改变好像没有什么作用,因为 FD2 的默认输出和 FD1的默认输出本来都是 monitor,一样的!

但是,当 FD1 是其他文件,甚至是其他 FD 时,这个就具有特殊的用途了。请大家务必理解这一点。

 

如果 stdin, stdout, stderr 进行了重定向或关闭, 但没有保存原来的 FD, 可以将其恢复到 default 状态吗?

1.如果关闭了stdin,因为会导致退出,那肯定不能恢复。

2.如果重定向或关闭 1stdou1t和1stderr1其中之一,可以恢复,因为他们默认均是送往1monitor1(但不知会否有其他影响)。

如恢复重定向或关闭的 stdout: exec 1>&2 ,恢复重定向或关闭的stderr:exec 2>&1

3.如果stdout和stderr全部都关闭了,又没有保存原来的FD,可以用:exec 1>/dev/tty 恢复。

 

管道和重定向的区别

1.管道是把一个程序的输出作为另一个程序的输入。

2.重定向是把输出定向到文件或者标准流。

 

mktemp 命令

Linux mktemp命令用于建立暂存文件。 mktemp建立的一个暂存文件,供shell script使用。 mktemp命令专门用来创建临时文件,并且其创建的临时文件是唯一的。shell会根据mktemp命令创建临时文件,但不会使用默认的umask值(管理权限的)。它会将文件的读写权限分配给文件属主,一旦创建了文件,在shell脚本中就拥有了完整的读写权限,其他人不可访问(除了root)

mkfifo命令

创建命名管道

"FIFO"是一种特殊的文件类型,它允许独立的进程通讯. 一个进程打开FIFO文件进行写操作,而另一个进程对之进行读操作, 然后数据便可以如同在shell或者其它地方常见的的匿名管道一样流式执行.

 

管道是一种通信机制,用于进程间的通信(也可通过socket进行网络通信),表现出来的形式将前面的每一个进程的输出,直接作为下一个进程的输入

管道命令仅能处理stdout,而error则会忽略

 

FIFO不同于pipe的地方: 1)FIFO可以看作高级的管道。它突破了pipe的限制(只能用于同源进程之间的通信),可以给任意进程之间建立通信连接;

2)FIFO是一个实际存在于磁盘中的文件;而pipe是由进程创建的,依赖于进程的存活期。

 

因为它本质上是一个文件,所以进程用open函数来打开一个FIFO,并在打开时指定文件操作模式(只读,只写还是读写)。之后用read(write)函数来读(写)FIFO

当一个进程以只写方式打开FIFO文件,另一个进程以只读方式打开同一个FIFO文件,这样就建立了两个进程之间的通信管道。

 

实际工作时,pipe和FIFO基本相同的。下面是两者相同的性质:

1)当读一个写端已经被关闭的pipe(或者是读一个没有为写打开的进程的FIFO)时,在所有数据都被读取后,read返回0,以指示到达了文件结束处

2)当写一个读端已经被关闭的pipe(或者是写一个没有为读打开的进程的FIFO)时,write返回-1,并将errno设置为EPIPE

3)在写pipe或者FIFO时,常量PIPE_BUF规定了内核中管道缓冲区的大小。如果多个进程同时写一个管道(或者FIFO),要保证写的字节数不大于PIPE_BUF,这样多个写进程的数据不会相互穿插而造成混乱

4)open使用O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会返回ENXIO 错误代码。没有使用O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。

 

 

 

可以将输出信道化到不同终端、

 

 

 

read命令

要与Linux交互,脚本获取键盘输入的结果是必不可少的,read可以读取键盘输入的字符。

read [-rs] [-a ARRAY] [-d delim] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [var_name1 var_name2 ...]

read命令用于从标准输入中读取输入单行,并将读取的单行根据IFS变量分裂成多个字段,并将分割后的字段分别赋值给指定的变量列表var_name。第一个字段分配给第一个变量var_name1,第二个字段分配给第二个变量var_name2,依次到结束。如果指定的变量名少于字段数量,则多出的字段数量也同样分配给最后一个var_name,如果指定的变量命令多于字段数量,则多出的变量赋值为空。

如果没有指定任何var_name,则分割后的所有字段都存储在特定变量REPLY中。

使用示例:

(1).将读取的内容分配给数组变量,从索引号0开始分配。

(2).指定读取行的结束符号,而不再使用换行符。

由于没有指定var_name,所以通过$REPLY变量查看read读取的行。

(3).限制输入字符。

例如,输入了5个字符后就结束。

如果输入的字符数小于5,按下回车会立即结束读取。

但如果使用的是"-N 5"而不是"-n 5",则严格限制读满5个字符才结束读取。

(4).使用-p选项给出输入提示。

"-p"选项默认不带换行功能,且也不支持"\n"换行。但通过$'string'的方式特殊处理,就可以实现换行的功能。例如:

关于$'String'$"String"的作用如下:

 

有些时候在某些服务管理脚本中看到$"$string"$"string",经过一些测试,又发现引号外面的$有和没有是一样的。一直也没去找究竟,刚才有人问了我,于是就去翻了下man bash,找到了解释。

(1).如果没有特殊定制bash环境或有特殊需求,"string""string"使""只是为了保证本地化。

以下是man bash关于$""的解释:

(2).还有'string',这在bash中被特殊对待:会将某些反斜线序列(如\n,\t,"'等)继续转义,而不认为它是字面符号(如果没有$符号,单引号会强制将string翻译为字面符号,包括反斜线)。简单的例子:

以下是man bash里关于$'的说明:

 

 

(5).禁止反斜线转义功能。

(6).不回显输入的字符。比如输入密码的时候,不回显输入密码。

另一种方法

(7).将读取的行分割后赋值给变量。

(8).给出输入时间限制。没完成的输入将被丢弃,所以变量将赋值为空(如果在执行read前,变量已被赋值,则此变量在read超时后将被覆盖为空)。

(9).读文件

 

 

lsof命令

 

shell脚本实现程序并发执行

循环实现并发程序:

并发的实现原理是将进程放到后台运行,从而不影响当前shell的运行。在shell脚本中有&符号可以实现这个操作。

上述脚本已经可以产生并发的效果,但是如果并发数量过于庞大,那么系统将无法处理,你将看到系统CPU负载极高的直观现象。

程序的并发数量并不是无限的,进程过多会导致机器卡死,所以需要控制并发的数量。可以使用管道来实现控制并发的数量

原理:

linux管道文件特性制作队列,控制线程数目

1.管道具有存一个读一个,读完一个就少一个,没有则阻塞,放回的可以重复取,这正是队列特性,但是问题是当往管道文件里面放入一段内容,没人取则阻塞,这样你永远也没办法往管道里面同时放入10段内容(想当与10把钥匙),解决这个问题的关键就是文件描述符了。

2.mkfifo /tmp/fd1

创建有名管道文件exec 3<>/tmp/fd1,创建文件描述符3关联管道文件,这时候3这个文件描述符就拥有了管道的所有特性,还具有一个管道不具有的特性:无限存不阻塞,无限取不阻塞,而不用关心管道是否为空。也不用关心是否有内容写入引用文件描述符:&3可以执行n次echo >&3往管道里放入n次内容

其中