Linux操作系统(七):BASH与Shell
- 认识BASH
- Shell的变量
- 命令别名与历史命令
- Bash shell的操作环境
- 数据流重定向
- 管道命令
- 限制用户使用系统资源:ulimit
一、关于本文内容的导读
这部分不涉及具体内容的解析,只是作为浏览和查找相关知识点的引导内容,采用【主题 | 命令 | 对应内容小节编号】三个关键信息的组合模式,依照这些信息可以快速查找到相关详细的示例和解析。
查询命令 | type | 2.3
输出一行文本 | echo | 3.1
输出环境变量信息 | env | 3.2
输出全部变量信息 | set | 3.2
创建键盘输入变量 | read | 3.4
设置变量类型 | declare/typeset | 3.4
创建命令别名 | alias | 4.1
取消命令别名 | unalias | 4.1
查看和管理历史命令 | history | 4.2
重新读取配置文件 | source | 5.4
查看和设置终端配置 | stty | 5.6
数据切片 | cut | 7.1
基于行的数据切片及过滤 | grep | 7.1
数据排序 | sort | 7.2
数据去重 | uniq | 7.2
数据统计 | wc | 7.2
删除替换字符 | tr | 7.3
过滤反向转义字符 | col | 7.3
基于行和公共字符的数据拼接 | join | 7.3
基于行的数据拼接 | paste | 7.3
将【tab】转换成空格 | expand | 7.3
划分/切割文件 | split | 7.4
从标准输出重建命令行 | xargs | 7.5
双重定向 | tee | 7.6
限制用户bash的资源配额设置 | ulimit | 8.*
二、认识BASH
BASH是Bourne Again SHell的简称,这里我基于这个名称来做一些不严谨的解释,shell表示壳程序、bourne again表示重新再做一遍,意思就是将这个壳程序重做一次,这个重做包含两层意思,一是将脚本程序转换成二进制可执行程序,二是将壳程序转换成真正的内核操作程序实现壳程序的操作;虽然这么理解有些牵强附会,但大概什么是BASH能有一些理解了吧。
其实关于BASH与Shell在Linux操纵系统(二)的3.*中就有了一些简单的解析和介绍,这里再从其与操作系统的关系和具体功能的角度来详细的介绍以下,前面提到了Shell是壳程序,那什么是壳程序呢?
我们知道计算机提供的计算资源是一堆硬件设备,在这些硬件设备上通过内核调用硬件设备的接口工作实现用户想要实现的具体的工作,而内核并不会像人一样理解我们的口语或者其他人类行为去做工,它也是给我们一堆的接口,然后我们通过命令、桌面的操作、其他应用程序去调用这些接口再去调用硬件资源工作,这些调用内核的命令、光标、应用程序就像包裹在内核上的壳一样,所以这些程序我们都可以称呼它为壳程序。简单的来说就是壳程序并不实现具体的功能,它只是通过调用内核的API来实现它想要实现的功能。
至于为什么要使用Shell这好像对于使用Linux就像一句废话,首先相对于KDE(桌面操作)来说,Shell轻便、占用资源少、容易管理,前面两个就不用说了,至于容易管理你可以想想如果某个应用升级了,KDE是不是也要升级,不然新增的功能可能就没办法用,因为KDE本身就是将众多应用组合在一起的应用。至于使用应用程序来管理操作系统的通用性问题?毕竟Linux各个产商都提供了大同小异的Shell。
然后,远程管理问题和多个主机管理,以Shell的远程联机和远程桌面控制来说这就更加不用强调了,Shell肯定更快更方便。还有命令直接对应了内核程序接口,这对于学习Linux甚至操作系统都非常友好,可以更深入的了解操作系统的工作原理。
2.1Shell与/etc/shells
由于在UNIX时期版权问题,在各个操作系统中的Shell都有各自不同版本,比如Bourne Shell简称sh、在Sun操作系统中默认的是C shell简称csh、还有商业中常用的K shell、TCSH等。在Linux中使用的是基于GNU的架构下发展出来的Bourne Again Shell,简称bosh。
在CentOS7.x下有一个/etc/shells文件,这个文件描述了当前系统可用的Shell,为什么在一个系统中会使用多种Shell,这是因为在系统某些服务也会需要调用Shell来做一些操作,这些服务不一定就会使用当前服务默认的Shell,而是使用/etc/shells中其他shell或/sbin/nologin这个shell,比如使用FTP这个服务你不会希望用户使用FTP以外的主机资源,所以就会使用/bin/nologin这个shell,可以简单把nologin理解为一个功能不齐全的shell。
可以通过cat查看/etc/shells中的shell列表,还是可以通过cat查看/etc/passwd查看到用户登入使用的nologin或bash。
2.2Bash shell的功能
history:历史命令,可以通过上下键切换以前输入的命令,这些示例命令记录在用户home目录下的.bash_history当中;(应用可以通过历史命令来排查之前的操作问题)
命令与文件补全功能【tab】;
命令别名设置功能;
任务管理、前台、后台控制:(job control、foreground、background)
程序化脚本:(shell scripts)
通配符:(Wildcard)
以上这些功能在后面内容或往后的博客都会具体介绍。
2.3查询命令是否为Bash shell的內置命令:type
在bush内部集成了很多內置命令,内置命令可以理解为Bash的內置管理操作系统的功能接口,于此相对应的就是外部命令,外部命令是独立的可执行文件,当然还有一种就是Bash将外部的可执行文件通过命令别名的方式将其內置到Shell中,了解这些的作用就是可以用来查找命令和可执行文件,如果确认这些命令是內置命令还是外部命令(执行文件)就可以通过type这个命令来实现:
type [-tpa] name #name就是要查询的命令名称
type的选项解析:
:不加任何选项,type会显示出name是bash內置命令还是外置命令;
-t:输出命令的意义,包括file表示外部命令;alias表示命令别名;builtin表示bash的內置命令;
-p:如果后面接的name是外置命令,就会显示出全部文件名(即包括文件路劲)
-a:会由PATH变量定义的路径中,将含有name的命令列出来,包括alias。(打印信息包括不加选项和命令的路径文件名,前提是这个命令文件目录被添加到了PATH变量中)
type的示例:
type ls #ls 是 `ls --color=auto' 的别名
type -t ls #alias
type -a ls #ls 是 `ls --color=auto' 的别名 ls 是 /usr/bin/ls
type cd #cd 是 shell 内嵌
type -t cd #builtin
2.4命令执行与快速编辑
首先就是将很长的命令用多行拼接的方式来编辑,这在前面的博客中就已经多次使用过了,就是在一行的末尾使用"\"来标识转义【Enter】键,这样使用Enter就不再是执行命令了,而是跳转到下一行继续编辑命令;
然后就是使用【ctrl+u】、【ctrl+k】来实现向前向后删除命令,以及使用【ctrl+a】、【ctrl+e】将光标移动到命令的最前面和最后面;
三、Shell的变量功能
关于什么是变量我想是没有必要解析的了,这是基本是任何一种编程语言都必然存在基本要素。变量源自数学概念,一般包含名称和值两个部分,名称是在当前环境下的变量的唯一身份抽象;变量值是变量在当前环境下描述变量本质的数据,变量名指向的值实际上是这个数据存储空间的抽象地址。变量值一般概念下是变化的,它会随着计算由变量名指向不同的描述变量客观本质的数据存储空间的抽象地址。
上面这个解析是我基于对变量的理解的描述,不理解的话也没关系,这里只需要知道在Bash下也有变量存在,下面就来在shell下如何使用变量。
3.1变量的使用与设置
shell中使用变量的方式:${variatename}
shell中设置一般变量的方式:variatename=variatevalue
shell中设置保留特殊字符特性量变的方式:variatename="variate is ${variatename}" #双引号设置的变量内部特殊字符会保留原本特性
shell中设置不保留特殊字符特性变量的方式:variatename1='variate is ${variatename}?' #单引号设置的变量内部特殊字符不会保留原本特性
shell中扩展变量内容的方式:variatename="$variatename":variatevalue #相当于给一个变量设置了多个值,可以把他看作在数组上添加了一个元素
shell设置自定义环境变量,供其他子程序使用:export variatename #相当于依赖模块导出被依赖项,关于什么是子进程在shell的进程管理部分介绍
通常大写字符命名的变量位系统默认变量,这不是强制的语法规则,但建议不要将自定义的变量名称写成大写字母的字符;
shell取消变量,可以理解为删除当前环境中的变量:unset variatename
下面演示一些变量的创建,会使用到echo这个命令来将变量值输出:
aaa=10 #创建一个变量
echo ${aaa} #输出变量值:10
bbb="9 ${aaa}" #创建保留特殊字符特性的变量
echo ${bbb} #输出变量值:9 10
ccc=’9 ${aaa}' #创建不保留特殊字符特性的变量
echo ${ccc} #输出变量值:9 ${aaa}
ddd=1
ddd="$ddd":2 #扩展变量值
echo ${ddd} #输出变量值:1:2
演示将变量变成环境变量:
a=111
b=222
export a #将变量a设置为环境变量
bash #启动一个子进程
echo ${a} #输出:111
echo ${b} #没有任何输出
最后关于取消变量就不演示了,使用unset将各个创建的测试变量都取消吧,免得与以后的测试造成混乱;然后使用exit退出子进程。
这里扩展说明一些shell语法,变量值中使用的【:】类似在其他编程语言中的数组中的【,】用来间隔元素用的,这里就是用来多个变量值;然后有时候我们可能需要在一个命令里面使用另一个命令的值,了解变量以后你肯定会想到使用变量保存下这个值,然后在后面的命令里面使用这个变量,这个思路不错,但在日常的编程中我们并不会将这种数据保存下来,因为它会损耗内存,想想如果你执行的命令它输出的是很大的数据将会发生什么。解决这个问题的方式就是在一个命令内部使用【`commander`】,还可以使$(commander),比如下面的示例是用来进入到目前内核的模块目录:
cd /lib/modules/`uname -r`/kernel
cd /lib/modules/$(uname -r)/kernel
3.2环境变量的功能
所谓环境变量就是当前进程下和子进程都能使用的变量,于此相对应的就是只能给当前进程使用的变量,在shell中没有对这类变量有什么具体的定义,在其他编程语言中将这类变量称为局部变量或当前作用域的私有变量,shell的环境变量在其他编程语言中还有一种说法就是全局变量。怎么定义不重要,重要的就是知道环境变量是当前进程和子进程都能使用的变量,非环境变量只有当前进程下能使用。而前面3.1中手动创建的变量默认都是非环境变量,自定义变量可以通过export设置为环境变量。
在shell的默认变量中包含有环境变量也有非环境变量,可以通过env来查看当前进程的环境变量,可以通过set查看全部变量,这些默认变量有的对于管理Linux很重要,所以要对他们有所了解。
查看以及了解一些重要的环境变量:
env
环境变量的详细信息:
XDG_SESSION_ID=10 HOSTNAME=localhost.localdomain SELINUX_ROLE_REQUESTED= SHELL=/bin/bash TERM=xterm HISTSIZE=1000 SSH_CLIENT=192.168.1.102 29949 22 SELINUX_USE_CURRENT_RANGE= QTDIR=/usr/lib64/qt-3.3 QTINC=/usr/lib64/qt-3.3/include SSH_TTY=/dev/pts/0 QT_GRAPHICSSYSTEM_CHECKED=1 USER=tx LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36: PATH=/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin MAIL=/var/spool/mail/root PWD=/root LANG=zh_CN.UTF-8 KDEDIRS=/usr SELINUX_LEVEL_REQUESTED= HISTCONTROL=ignoredups HOME=/root SHLVL=4 LOGNAME=tx QTLIB=/usr/lib64/qt-3.3/lib SSH_CONNECTION=192.168.1.102 29949 192.168.1.103 22 XDG_DATA_DIRS=/root/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share LESSOPEN=||/usr/bin/lesspipe.sh %s DISPLAY=localhost:10.0 XDG_RUNTIME_DIR=/run/user/0 QT_PLUGIN_PATH=/usr/lib64/kde4/plugins:/usr/lib/kde4/plugins XAUTHORITY=/root/.xauthF2R1sN _=/usr/bin/env
一些重要的环节变量解析:
HOME:表示用户的根目录;
SHELL:表示当前使用的shell是哪一个,默认是bash;
HISTSIZE:表示能记录记录多少条历史命令(后面会介绍什么是历史命令);
MAIL:表示当前用户的邮箱文件;
PATH:表示执行文件的查找目录;
LANG:表示当前环境使用的语系编码字符集;
RANDOM:表示随机数变量,通过($RANDOM)可以获得一个随机数值;
HOSTNAME:表示当前的主机名;
TERM:表示这个终端使用的环境是什么类型;
OLDPWD:表示上一个工作目录;
USER:表示当前使用Linux的用户的名称;
LS_COLORS:表示一些可显示的颜色;
PWD:表示当前所在的目录;
LOGNAME:登入者用来登入的名称;
_:表示上一次使用的命令;
查看所有变量及一些重要的默认非环境变量:
set
这里就不展示详细的打印结果了,重点来关注一些比较重要的默认非环境变量:
BASH VERSINON:表示bash的版本;
COLUMNS:表示当前终端环境每栏的字符长度;
HISTFILE:表示历史命令存放的文件;
HISTFILESIZE:表示历史命令文件中最大能存储多少条;
HISTSIZE:表示目前环境下,内存中记录的历史命令最大条数;
IFS:表示默认的分隔符号;
LINES:目前终端的最大行数;
MACHTYPE:安装的机器类型;
OSTYPE:操作系统的类型;
PS1:表示命令提示字符;(就是输入命令前的这个内容"[tx@localhost ~]$",它是一个表达式,根据用户、主机名、以及当前的目录计算出具体的内容,可以尝试使用echo打印以下这个表达式看看)
PS2:表示如果使用转义字符(\)转换【Entr】的输入,跳转到第二行的的提示符;
$:表示目前shell使用的PID;
?:表示刚刚执行完命令的返回值;
然后再来关注以下PS*这个变量,在前面的解释中我们提到了它是表示的内容,而且是是以表达式的方式来设置的,在我们没有手动设置的情况下PS1就是”[\u@\h \W]\$“,它的表达式还可以包含很多其他特殊字符,通过这些特殊字符可以自定义PS*的表达式,来说设置PS*的信息:
\d:可显示出【星期 月 日】格式的日期信息;
\H:完整的主机名(例如:study.centos.vbird);
\h:仅主机名(完整的主机名第一个字段);
\t:显示时间,24小时格式【HH:MM:SS】;
\T:显示时间,12小时格式【HH:MM:SS】;
\A:显示时间,24小时格式【HH:MM】;
\@:显示时间,12小时格式【am/pm】;
\u:目前用户的账号名称;
\v:bash的版本信息;
\w:完整的工作目录名称;
\W:利用basename函数获取的工作目录名称,仅列出最后一个目录名;
\#:执行的第几个命令;
\$:提示符,如果是root时,提示符为#,否则为$;
最后在这里还是提一下关于输出信息与语系编码的问题,其实这部分在Linux操作系统(二):初步了解Linux操作系统的2.2中就有介绍,可以使用locale来查看但概念系统支持的语系与字符编码,如果需要调整的话可以使用cat命令编辑/etc/locale.conf来实现配置,具体这里就不演示了,因为这个问题一般在安装系统时就解决了,如果不知道怎么具体设置的话查找一些相关具体的文档参考吧。
3.3变量的有效范围
关于变量的有效范围在3.2中其实已经介绍过了,就是环境变量和非环境变量的相关内容,其原理是当shell启动时会在内存中分配一片内存区域共这个shell的进程使用,变量就会被写入到这个内存中,当再在这个shell上加载另一个shell同样也会在内存中分配一片内存供这个子进程使用,并且会将父进程中的环境变量导入到这个子进程shell的内存空间中。比如PS*不是环境变量,所以它就不会被导入到子进程的内存空间中。
3.4变量键盘读取、数组、声明:read、array、declare
使用read命令读取来自键盘输入的变量
键盘输入变量是什么意思呢?就是有些变量的值并不是一开始就设定好的或由程序自动生成的,而是需要在用户在操作过程中来输入的,设置键盘输入变量的命令就是read,下面来看看read的语法(內置命令):
read [-pt] variablename
read命令的选项和参数解析:
-p:用来设置提示字符; -t:可以设置输入等待时间,单位(秒);
variablename:要设置的变量名称;
使用read命令设置键盘输入变量的示例:
read a #按下【Enter】以后终端会等待键盘输入变量a的值,输入完以后再按下【Enter】键,就在当前环境下生成了一个变量a,这个变量值就是刚刚输入的数据
echo ${a} #测试查看变量a的值
read -p 请输入变量b的值: -t 15 b #这个回车后会在下一行最前面出现提示信息”请输入...“,并且必须在15秒内输入完成并按下【Enter】完成输入,否则就会判定无效
echo ${b} #测试查看变量b的值
声明变量类型declare、typeset
在shell中创建变量时或者创建变量之后可以设置变量类型,其类型包括字符串、数组、整数、环境变量、只读类型readonly,字符串是默认类型。声明变量类型的命令有两个declare和typeset,它们的功能都是一样的,下面来看语法:
declare [-aixrp] variablename[=bariablevalue]
declare/typeset命令的选项解析:
-a:将变量定义为数组类型array -i:将变量定义为整数类型integer -x:将变量定义为环境变量,同export命令作用一样 -r:将变量定义为不可被更改的内容,也不能unset -p:查看当前遍历的类型
shell声明变量类型的示例:
sum=1+2+3 #创建默认类型的变量sum
echo ${sum} #打印结果:1+2+3,因为默认情况下sum的类型是字符串,所以后面的求和并不是表达式,而是被作为一个字符串。
declare -i sum=1+2+3 #创建整数类型的变量sum,这个sum会覆盖前面的sum;
echo ${sum} #打印结果:6
declare -p sum #查看变量sum的类型:declare -i sum="6"
declare -x sum #将变量sum定义为全局变量
declare -p sum #查看变量sum的类型:declare -ix sum="6",你会发现变量类型是可以多个同时存在的,但也不是所有类型能同时存在
declare -a sum;declare -p sum #打印结果:declare -aix sum='([0]="6")',这个就表示为整型数组环境变量
declare +x sum;declare -p sum #打印结果:declare -ai sum='([0]="6")',可以使用+来取消-这种变量类型设置操作,但是不能取消-a和-r
declare -r sum
declare +r sum #打印结果:-bash: declare: sum: 只读变量,这一步谨慎操作,-r类型的变量不能取消也不能被unset删除变量,只能注销用户登入才能恢复
关于注销恢复在后面的进程管理部分会由相关的解析,再来看看关于数组类型的声明和使用:
declare -a arr[0]=a0;declare -a arr[1]=a1;declare -a arr[2]=a2;
echo "${arr[0]},${arr[1]},${arr[2]}" #打印结果:a0,a1,a2
3.5变量内容的删除、取代、替换
在很多时候有些获取到的数据并不完全符合我们的需求,需要做一些处理,这部分内容就是将在bash中如何实现删除、取代、替换数据的部分内容。
基于#、##与%、%%规则的内容删除匹配:
#:表示从开头开始匹配符合匹配字符的最短数据删除 ##:表示从开头开始匹配符合匹配字符的最长数据删除 %:表示从末尾开始匹配符合匹配字符的最短数据删除 %%:表示从末尾开始匹配符合匹配字符的最长数据删除
基于-、+、=、?、:规则的内容替换匹配:
【-】:表示如果变量未设置,使用指定的值作为新创建变量的值,否则使用变量的值作为新创建变量的值;
【+】:表示如果变量得值与指定的值不匹配时,就是用指定的字符替换作为新创变量的值,当变量不存在时新创建变量的值为空;
【=】:表示如果变量未设置,使用指定的值作为新创建变量的值,当变量存在时使用变量的值作为新创建变量的值,但需要注意空值不会赋给新变量,在变量为空值时给新变量赋指定的值;
【?】:表示如果变量未设置,则基于错误提示生成bash错误打印到终端上,新变量不会被创建;当变量存在时,则使用变量的值作为新创建变量的值;
【:】:表示当变量设置的值为空字符串时(即【""】),被视变量未创建;
示例:
#示例一:关于#、##、%、%%的变量内容获取规则
echo ${PATH} #输出:/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
echo ${PATH#/*/lib64/qt-3.3/bin:} #输出:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
echo ${PATH##/*:} #输出:/root/bin
echo ${PATH%:*} #输出:/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
echo ${PATH%%:*} #输出:/usr/lib64/qt-3.3/bin
#示例二:基于-、+、=、?、:的变量创建与变量值替换
abc=111 #创建一个有赋值的原变量
def="" #创建一个赋空值的原变量
cba=${ghi-222};echo ${cba} #输出:222
cba=${abc-222};echo ${cba} #输出:111
cba=${def-222};echo ${cba} #没有数据输出
cba=${def:-222};echo ${cba} #输出:222
cba=${ghi+222};echo ${cba} #没有数据输出
cba=${abc+222};echo ${cba} #输出222
cba=${def+222};echo ${cba} #输出222
cba=${def:+222};echo ${cba} #没有数据输出
cba=${ghi=222};echo ${cba};echo ${ghi} #输出:222 222
cba=${abc=222};echo ${cba};echo ${abc} #输出:111 111
cba=${def=222};echo ${cba};echo ${def} #输出:222 【def没有数据输出】;cba的值来源上一个测试数据,这行不会给它赋值
cba=${def:=222};echo ${cba};echo ${def} #输出:222 222
unset ghi;cba=${ghi?变量不存在};echo ${ghi};echo ${cba}; #输出错误信息:-bash: ghi: 变量不存在,因为第二个命令输出了错误信息,后面的命令就不会在执行
echo ${ghi};echo ${cba}; #输出:222,这里输出的数据是cba之前测试生成的变量值
cba=${abc?变量不存在};echo ${cba}; #输出111
def="";cba=${def?变量不存在};echo ${cba}; #没有数据输出,因为变量存在,cba获得了def的空值
cba=${def:?变量不存在};echo ${cba}; #输出错误信息:-bash: def: 变量不存在
echo ${cba}; #没有任何值输出
四、命令别名与历史命令
4.1命令别名
#创建命令别名的命令:alias
alias lm='ls -al | more' #这里关于more查看man的文档了解即可
#取消命令别名的命令:unalias
unalias lm
4.2历史命令:history
history [n]
history [-c]
history [-raw] histfiles
history的选项解析:
-n:表示查看最近n条命令行表;
-c:将目前shell中所有历史命令清除;
-a:将当前新增的历史命令写入到histfiles中;
-r:将histfiles中保存的历史命令读到当前的shell的历史命令中
-w:将目前的历史命令写入到histfiles中;
需要注意的是,history默认情况下会将历史命令写入到~/.bash_history,如果指定了其他文件就会基于指定的history来处理相关操作。
关于历史命令的相关机制:
1.登入Linux主机后,系统会主动从home目录下读取~/.bash_history作为历史目录;
2.~/.bash_history记录的历史命令的最大容量由bash的HISTFILESIZE变量决定;
3.当注销登入时,会将新增的命令写到~/,bash_history的最后面,如果当前历史命令已经超过最大容量,就会将最前面的历史命令清除;
4.除了注销时自动写入,也可以使用history -w强制写入磁盘;
5.同一个账户在多个端口上登入的话,只有最后一个注销的bash中的历史命令写入到~/.bash_history中;
6.历史命令不会记录执行时间;
关于历史命令的便捷使用方法:
!number #执行历史命令中指定编号的命令
!command #执行最近的这个命令(使用原来的选项和参数执行)
!! #执行上一个命令
五、Bash shell的操作环境
5.1命令环境:由PATH变量提供的文件路径的外部命令、bash自身內置命令builtin、历史命令,由于alias来实现內置命令和外部命令的查找及历史命令机制来实现。
5.2登入信息与欢迎信息:bash的登入信息由/etc/issue提供,欢迎信息由/etc/motd提供,并且这两个配置文件都可以自定义编辑。
5.3bash的配置文件:
关于bash的配置文件就不得不先了解两种登入模式:login shell、non-login shell,为什么会有两种登入模式,这与配置文件又有什么关系呢?所谓登入就是系统基于用户输入的账号和密码在系统上创建一个用户进程,这种登入方式就是login shell。但也不是每次用户登入都需要输入账号和密码,当使用windows X的图形化操作方式登入系统后,然后通过图形化接口启动一个终端,这个终端的bash就不需要使用账号密码登入,这种登入方式就是non-login shell,还有在一个终端内通过bash命令启动一个bash子进程也是non-login shell。这两种登入方式读取的配置文件是不一样的,下面来逐个了解这些配置文件:
login shell相关的配置文件:
/etc/profile:用户登入取得bash进程时一定会读取的配置文件,它会基于用户表示UID来决定很多重要的变量数据,该文件的主要变量有:
PATH:会根据UID决定PATH要不要包含有sbin的系统命令目录;
MAIL:根据账号设置好用户的mailbox到/var/spool/mail/账号名称;
USER:根据用户账号设置此变量;
HOSTNAME:根据主机的hostname命令决定此变量内容;
HISTSIZE:历史命令的最大容量;
umask:包括root默认为022而一般用户为022等;
~/.bash_profile:bash读取完换进配置的/etc/profile后并借此调用其他配置文件,也就是读取用户的个人配置,分别按照顺序读取:~/.bash_profile、~/.bash_login、~/.profile,但要注意并不是三个文件都读取,它是按照顺序检查这些配置文件是否存在,如果检查到存在就是用该配置文件。
non-login shell相关的配置文件:
~/.bashrc:这个配置文件规范了一些安全的命令别名、它会根据不同的UID设置umask的值及提示字符、调用/etc/profile.d/*.sh的设置。比如在有一些版本的Linux中,万一没有~/.bashrc或不小删除了,提示符就可能使bash的版本号,这时候就可以通过复制/etc/skel/.bashrc到根目录下,并自定义提示符内容,然后再使用source命令去调用~/.bashrc就会恢复。
login shell 与non-login shell共用的相关配置:
/etc/profile.d/*.sh:这表示多个sh扩展名的文件,如果用户对这些文件有r权限就会被/etc/profile调用。这个目录下的配置文件规范了bash的界面颜色、语系、ll与ls的命令别名、vi的命令别名、which的命令别名。如果需要给所有用户设置一些共享命令别名,就可以在这个目录下自定义建立扩展名为.sh的文件来实现,并将所需要的数据写入即可。
/etc/locale.conf:这个配置文件由/etc/profile.d/lang.sh调用,这是决定bash默认语系编码的重要配置文件,文件中最重要的就是LANG/LC_ALL这个变量的设置;
/usr/share/bash-completion/completions/*:还记得前面提到的【tab】命令补齐、文件名补齐、选项参数补齐等功能吗?就是从这个目录下找到相对应的命令来处理,这个目录下的内容由/etc/profile.d/bash_completion.sh文件加载;
5.4手动读取配置文件:source:
有些时候我们会修改一些配置文件,由于配置文件必须要在登入系统时才会被读取,按照这个逻辑就是如果要使用更新的配置文件就需要注销再重新登入,实际上不需要,可以使用source命令主动去读取配置信息:
#比如读取配置文件~/.bashrc的配置信息,以下两个方式都可以
source ~/.bashrc
. ~/.bashrc
5.5一些其他的配置文件
/etc/man_db.conf:这是一个与系统管理员相关的配置文件,比如规范了man命令man page的路径去哪里寻找。
~/.bash_history:历史命令,这个就不过多解释了,详细参考4.2;
~/.bash_logout:这个配置文件记录了当注销bash后,系统自动完成了哪些操作后才离开的。
5.6终端环境设置:stty、set
stty -a #查看快捷键及对应功能,比如可以查看到erase = ^?; 向后删除字符的快捷键,查看你会发现还有很多功能没有定义快捷键方式
stty erase ^h #这个表示将向后删除操作的快捷键改成【Ctrl+h】,不建议操作,如果测试了的话记得改回【Ctrl+?】,stty 功能名称 快捷键实现自定义快捷键
set [-uvCHhmBx] #还记得在前面使用set查看非环境变量吧,其实set是用来终端的一些配置值,也就是一些终端变量值,还可以用来设置命令输出/出入环境
关于set的更多使用查看官方文档吧,这里一些常用的bash快捷键组合列出来:
【Ctrl+c】:终止目前的命令; 【Ctrl+d】:输入结束,如邮件结束的时候;
【Ctrl+m】:回车;
【Ctrl+s】:暂停屏幕的输出;
【Ctrl+q】:恢复屏幕的输出;
【Ctrl+u】:在提示字符下,将整列命令删除;
【Ctrl+z】:暂停目前的命令;
5.7通配符与特殊符号
#通配符 *:表示0个或多个任意字符; ?:表示一定有一个任意字符; []:表示一定有一个中括号内的任意字符; [-]:表示一定有一个从减号前到减号后字符之间的字符,其表达范围是基于连续的字符编码集 [^]:当第一个字符为指数符号(^),表示反向选择,即表示非中括号内任意字符; #特殊字符 #:注释符号; \:转义符; |:管道,分隔两个管道命令的符号; ;:连续命令执行分隔符; ~:用户home目录; $:使用变量前导符; &:任务管理,将任务变成后台任务; !:逻辑运算符”非“not的意思; /:目录符号; >、>>数据流重定向,输出定向,分别是【替换】与【累加】; <、<<数据流重定向,输入定向; '':单引号,不具有变量替换的功能; "":双引号,具有变量替换的功能; ``:表示可以先执行的命令,也可以使用$(); ():在小括号内为子shell的起始于结束; {}:在大括号内为命令区块的组合
六、数据重定向
关于数据重定向在文件系统压缩那一篇博客就有应用过,在通常情况属下终端的数据默认输出到当前的终端窗口上,这种默认输出就叫做标准输出,标准输出又有两种情况,一种是输出标准的正确数据standard output,另一种是输出标准的错误信息standard error output,然后还有一个标准输入,就是通过键盘的输入的方式standard input。
然而在很多实际应用中,我们并不想将数据全部输出到终端的屏幕设备上,而是系统将一些数据输出到指定的文件或其他设备,而关于输入有时候也不仅仅是依靠键盘,可能是希望将某个文件数据输入到指定的设备和文件,这种非标准的输出与输出就是重定向。
比如我们在管理系统是不希望将所有正确的数据和错误的信息输出到终端屏幕上,那样会显得很乱,不好管理,这时候就可以使用重定向将错误信息输出到指定的文件,这些被输出到指定文件的数据就不再会在终端屏幕上打印出来了,如果想要查看这些错误信息就到指定的文件查看即可。
标准输入:代码为0,使用<或<<; 标准输出:代码为1,使用>或>>,完全的写法是1>或1>>; 标准错误输出:代码为2,使用2>或2>>;
关于数据重定向的示例:
#标准输出重定向示例:
ll / > ~/rootfile #将ll命令查询的根目录的信息输出到home目录下的rootfile文件中
ll /home > ~/rootfile #注意这个文件中之前根目录的信息没有了,这种因为>还有一个特性就是会覆盖原来的数据
ll / >> ~/rootfile #再查看以下文件中记录的信息,这时候就同时包含了/home和/的数据了,这是因为>>还有表示累加的功能
#标准错误输出重定向示例,下面的示例使用普通用户身份测试
find /home -name .bashrc > list
#分别将标准输出和标准错误输出重定向到不同的文件中
find /home -name .bashrc > list_right 2> list_error #表示当标准输出时数据重定向写到list_right中,当标准错误输出时数据重定向写到list_error中
#将标准输出和标准错误输出都重定向到一个文件中
find /home -name .bashrc > list 2>&1
find /home -name .bashrc &> list
#使用标准输入的方式创建文件写入数据
cat > catfile
#使用标准输入重定向的方式创建文件写入数据
cat > catfile < ~/.bashrc #将.bashrc的输入写入到catfile中
#<<表示结束输入
cat > catfile << "eof" #这是基于标准输入创建文件并写入数据,当输入结束时在最后使用单独的一行输入eof然后回车就可以结束输入,而不再需要【ctrl+d】来结束
最后在这里提一下;、&&、||的使用,关于这三个符号如果编程经验的就明白,它通常也被称为逻辑符号,分号(;)表示分隔多个命令,回车后依次执行多个命令;&&通常被称为“并”,当前面一个条件成立时,后面的操作就会继续,比如两个命令中间使用&&这个符号(例如com1 && com2),只有当com1输出正确才会执行com2,如果com1输出错误信息com2就不会执行;||通常被称为“或”,即如果两个命令之间使用||这个符号(例如:com1 || com2),当com1发生错误时com2就会执行,相反如果com1执行正确时com2就不会执行了。
七、管道命令:pipe
关于管道命令先要了解管道在这里的含义,简单的来理解就是负责将数据从一个地方传输到另一个地方,在传输的过程中包括读取数据的缓冲控制、传输控制、数据写入控制、数据处理。当然要想实现管道传输方案,还要将数据进行切片处理,这是管道传输的基础。
在bash中less、more、head、tail等命令都是能接受标准输入的管道命令,管道命令不会接受标准错误的数据输入,管道命令需要能够接受来自前一个命令的数据称为它的标准输入才能继续处理。管道传输是实现数据流模式的重要基础,所以很多编程语言中将关于管道传输相关的原理介绍都放在与流相关的内容中一起介绍。
关于管道传输的原理这里不做详细介绍,如果有兴趣可以参考我之前基于nodejs的一篇关于管道传输的博客: nodejs的tream(流)解析与模拟文件读写流源码实现。
7.1选取命令:cut、grep
cut从字面意思就能看出它是负责对数据做切片处理的工具,所谓切片就是将数据切成n个小片段,这样方便传输和处理,下面来看cut命令的语法和选项:
cut -d '分隔字符' -f fields
cut -c 字符区间
cut的选项及参数说明:
-d:用于指定基于指定的字符分隔数据,与-f配合使用; -f:用于取出指定的数据片段; -c:用于取出指定范围的字符片段(基于每行切片再按照指定范围获取数据片段);
关于cut还有一些其他选项和参数,详细参考man文档,这里来看几个示例:
#基于基于冒号":"切割PATH变量中的每个文件地址
echo ${PATH} | cut -d ':' -f 3,5 #需要注意的是-f的数据段编号是从1开始的
export | cut -c 12- #表示从每行的第12个字符开始,-c除了指定开始的位置还可以指定范围的结束位置,可以尝试测试-c 12-20的取值范围
grep是基于行将数据切片,并筛选出包含指定字符的数据。
grep [-acinv] [--color=auto] '查询字符' filename
grep的选项与参数解析:
-a:将二进制文件以文本文件的方式查找数据;
-c:计算找到'查询字符'的次数;
-i:忽略查询字符的大小写;
-n:输出数据的原行号;
-v:反向选择,即筛选出没有'查询字符'的那一行;
--color=auto:将查询关键字部分加上颜色;
关于grep其实在之前的文件系统压缩中就使用过,可以查看之前的那篇博客加深理解,下面就写一个简单的示例:
last | grep 'root' #查看root最近的登入信息
7.2排序命令:sort、wc、uniq
这三个命令真正实现排序功能的是sort,wc用于统计数据量,uniq用于筛选连续重复的数据,这三个命令在管道传输中属于数据处理器。
sort排序命令的语法:
sort [-fbMnrtuk] [file or stdin]
sort命令的选项与参数解析:
-f:忽略大小写; -b:忽略前面的空格字符; -M:以月份名称排序; -n:使用纯数字排序; -r:反向排序; -u:就是uniq,连续相同的数据仅输出一个; -t:分隔符号,默认使用【tab】键来分割(这个分隔是在每个数据片段内部来分割的,用来配合-k的基于某个区间排序的); -k:以那个区间来排序的意思;
sort命令的示例:
cat /etc/passwd | sort #默认情况下基于所有字符逐个排序
cat /etc/passwd | sort -t ':' -k 3 -n #如果不使用-n就会基于字符排序,8和80就会被排成相邻的数据,使用-n就会按照第三个区间的数值排序
uniq去重命令语法:
uniq [-ic]
uniq命令的选项解析:
-i:忽略大小写;
-c:进行计数,就是指记录该片段重复出现的次数;
uniq命令的示例:
last | cut -d ' ' -f 1 | sort | uniq #这个命令所实现的就是将last输出的每行数据使用空格切割并取出第一个片段输出,然后排序再取出不重复的数据
last | cut -d ' ' -f 1 | sort -u #这行命令实现的操作与上一行命令一致,但sort没有计数功能,如果要使用计数功能还是需要使用uniq命令
last | cut -d ' ' -f 1 | sort | uniq -c
wc统计命令语法(默认将数据的行数、英文字母的字数、基于字符统计的字数):
wc [-lwm]
wc的选项解析:
-l:仅列出行; -w:仅列出多少英文字母的个数; -m:仅列出多少字符;
wc命令的示例:
last | grep 'root' | wc -l #这相对于uniq命令的“last | cut -d ' ' -f 1 | sort | uniq -c”数据统计就简练很多了,但只能统计一个用户
7.3字符转换命令:tr、col、join、paste、expand
tr命令删除或替换字符:
tr [-ds] SET1 ...
tr命令选项解析:
-d:删除数据中SET1这个字符;
-s:替换重复的字符,就是在匹配的时候SET1的字符连续重复出现,使用-s后就可以实现去重;
tr命令的示例:
last | tr '[a-z]' '[A-Z]' #这行命令可以将原数据小写字母替换成大写字母,也就是tr不使用-d的时候并且使用了SET2的情况下,就会使用SET2替换SET1,且字符逐个对应
echo ${PATH} | tr -d ':' #这行命令仅用于测试,实际应用中不会出现
a='aaa'; echo ${a} | tr -s 'a' #创建一个变量a,并使用tr将连续出现的指定字符实现去重,-s实现去重同时也可以使用SET2实现替换操作
col命令实现过滤反向转换符,用于过滤掉反向符和半反向符:
col [-xb]
col还有一些其他选项,详细可以参考man的文档,这里仅对语法列出来的选项做解析:
-b:不输出任何退格符,在每列的位置只打印最后写的那个字符;
-x:输出多个空格替换tab;
col命令的示例:
cat -A /etc/man_db.conf #使用cat命令的-A选项将测试数据打印出来,可以看到打印信息中有“^I^I”,这就是反向转义字符的一种【tab】
cat etc/man_db.conf | col -x | cat -A | more #这是通过col将【tab】转换成空格的结果,后面的cat是接收col输出的管道数据,然后通过more过滤器分片段查看
join命令实现将两个公共字段拼接:
join [-ti12] file1 file2
join命令的选项和参数解析:
-t:基于指定字符将两个文件的数据切割成片段,默认使用空格将数据切割; -i:忽略大小写差异; -1:数字1表示指定匹配第一个文件的片段,默认使用第一个片段(即-1 1); -2:数字2表示指定匹配第二个文件的片段,默认使用第一个片段(即-2 1);
join会将两个文件的没一个行对应匹配,如果没有公共片段就只保留第一个文件的数据,公共片段只保留一个,下面来看具体的示例:
head -n 3 /etc/passwd /etc/shadow #先使用head命令查看测试的原数据
join -t ':' /etc/passwd /etc/shadow | head -n 3 #这里使用公共片段都是用默认的一个片段,实际全部表达为join -t ':' -1 1 /etc/passwd -2 1 /etc/shadow | head -n 3
paste命令实现将两个数据的每一行对应拼接:
paste [-d] file1 file2
paste命令的选项和参数解析:
-d:用于指定拼接数据的字符,默认使用【tab】来分隔;
-:如果file使用-,表示来自标准输入的数据;
paste命令的示例:
paste /etc/passwd /etc/shadow
expand命令实现将【tab】转换成空格:
expand [-t] file
expand命令选项和参数解析:
-t:指定将一个【tab】转换成几个空格
关于expand命令肯定会想到前面的实现反向字符过滤的col命令,可以将expand的一个特例,但expand自身还扩展提供了一个指定空格个数的选项,下面来看示例:
grep '^MANPATH' /etc/man_db.conf | head -n 3 | expand -t 6 - | cat -A
7.4划分文件的命令:split
split [-bl] file PREFIX
split命令的选项及参数解析:
-b:指定划分文件的大小,可以加单位b、k、m等; -l:以行数来划分文件; PREFIX:生成文件的前缀字符;
在管道命令中前面可能会感觉split与cut类似,但实质上完全两回事,cut时将数据切片后将切片的数据用管道的方式输出,而split是将数据切割后生成文件,下面来看示例:
cd /tmp; split -b 300k /etc/services services
ll -k services*
7.5参数代换:xargs
xargs [-0epn] command
xargs命令的选项及参数解析:
-0:将输入的stdin含有特殊字符如`、\、空格等时,可以将这些特殊字符还原成一般字符; -e:这是EOF(end of file)的意思,当xargs分析到指定的字符时,就会停止工作; -p:在执行每个命令时,都会询问使用者的意思; -n:指定从管道接收几次数据执行一次command命令
xargs简单的来说就是用来基于管道输出的数据生成命令的参数,man的文档中简介是“从标准输出重建并执行命令行”,下面来看示例代码:
#从/etc/passwd文件中获取用户id,然后使用id查看用户的账号信息
cut -d ':' -f 1 /etc/passwd | head -n 3 | xargs -p -n 1 id
7.6关于减号【-】的用途:
简单的来说就是将管道前一个命令的输出(stdout)作为这一次的输入(stdin),【-】的位置就是数据输入的位置,比如下面这个实例:
mkdir /tmp/homeback
tar -cvf - /home | tar -xvf - -C /tmp/homeback
这个例子中【-】所表示的含义,将home下的文件打包,将打包的数据传送到stdout(第一个【-】),然后tar -xvf - -C /tmp/homeback中的【-】则接收第一个【-】输出的数据作为当前的输入stdin。
7.7双重定向:tee
在6.1中详细的介绍了重定向,>和>>是负责将数据保存到设备或文件,如果要使用这些数据就需要重新去访问文件系统,这种操作肯定不符合管道的传输思想,而tee也就是双重定向解决了这个问题,tee将数据流分别传输到文件和终端的标准输出上,下面来看tee的语法:
tee [-a] file
tee命令的选项和参数解析:
-a:以累加的方式将数据添加到文件中;
tee命令的示例:
last | tee last.list | cut -d " " -f1 #这个示例可以将last输出的数据保存到一个文件中,还可以将数据传递给cut做切片处理
八、限制用户使用系统资源:ulimit
因为Linux是多用户操作模式,为了保证每个用户能正常使用系统资源,就要对每个用户使用资源进行限制,如果不加以限制,十个用户同时开启100个文件,每个文件大小为10MB,那这时候就需要10*100*10=10000MB=10GB内存,这时候很可能就会直接导致系统挂了,因为默认情况下每个用户使用资源都是系统资源的最大容量,所以为了保证每个用户的正常使用就可以限制每个用户的bash使用的资源大小来解决。
配置用户的bash资源的工具就是ulimit,这个命令非常简单,可以使用ulimit -a来查看当前bash的每个资源的限额,就可以看到需要配置的资源有哪些,然后基于当前用户的具体应用配置相应的资源即可:
ulimit [-SHacdfltu] [配额]
ulimit命令的选项及参数解析:
-H:表示严格的设置,用户使用资源不能超过设置的数值; -S:表示警告的设置,用户使用资源可以超过设置的数值,但如果超过则会有警告信息提示; -a:后面不接任何参数可以查看当前所有配置的限制额度; -c:当某些程序发生错误时,系统可能会将程序在内存中的信息写成文件(除错用); -f:次shell可以建立的最大文件容量,单位为字节; -d:程序可以使用的最大段内存容量; -l:用于锁定lock的内存量; -t:可以使用的最大cpu时间,单位为秒; -u:单一使用者可以使用的最大进程数量;
关于具体的设置这需要根据具体情况而定,更多可设置的配置参考-a选项即可。