Bash与Shell

当我们使用计算机来播放音乐时,其实是使用shell发送指令给Kernel内核,由Kernel控制硬件来完成音乐播放;操作系统其实是一组软件,由于这组软件在控制整个硬件与管理系统的活动监测,若这组软件能被用户随意操作,若使用者应用不当,将会使得整个系统崩溃,故不能够随便被一些没有管理能力的终端用户随意使用;所以就有了在操作系统上面发展的应用程序,用户可通过应用程序来控制内核,让内核来完成所需要的硬件任务,也被称为壳程序shell

壳程序的功能只是提供用户操作系统的一个接口,包括man、chmod、chown、vi、fdisk、mkfs等指令都是独立的应用程序,但可以通过壳程序指令列模式来操作这些应用程序;即只要能够操作应用程序的接口都能够称为壳程序狭义的壳程序指的是命令行方面的软件,广义的壳程序则包括图形接口的软件;

文本接口shell的好处,几乎所有的Linux的发行版本使用的bash都是一样的;Linux常常需要通过远程联机来管理,联机时文本接口的传输速度快且不容易出现断线或信息泄露;

早期Unix发展者众多,第一个流行的shell是由Steven Bourne发展出来的的Bourne shell简称sh,后来另一个由柏克莱大学Bill Joy设计依附于BSD版的Unix系统中语法类似C语言C shell简称csh,故诞生了很多版本的shell,如Bourne SHell(sh)、故诞生了很多版本的shell,在Sun中预设的C SHell、 商业上常用的K SHell、以及TCSH等;Linux使用的是Bourne Again SHell简称bashbashBourne Shell增强版确且是基于GNU架构的;

  • /bin/sh:已经被/bin/bash取代
  • /bin/bash:Linux预设的shell
  • /bin/tcsh:整合C Shell,提供更多的功能
  • /bin/csh:已经被/bin/tcsh取代

系统中合法的shell都要被写入到/etc/shells文件中,因为系统某些服务在运行过程中会检查使用者能使用的shells,而这些shell的查询都是基于/etc/shells文件;若不想某些程序使用主机资源,可以给其分配一个错误的shell如/sbin/nologin当在终端tty上登录系统后,Linux会根据/etc/passwd文件的设定分配一个预设的shell/etc/passwd最后一个冒号后面的数据,就是登录后获取的预设的shellroot用户预设的是/bin/bash

1
2
3
4
cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

bash shell功能

/bin/bashLinux预设的shell,是GNU计划中重要的工具软件之一,也是目前Linux发行版本的标准shell兼容sh,且根据一些使用者需求加强过,支持许多的通配符ls -l /usr/bin/X*;,能记忆使用过的命令~/.bash_history中,~/.bash_history记录的是上一次登录所执行过的指令,当前登录所执行的指令都被暂存在内存中,当成功注销后才会记录到.bash_history中,默认可记忆1000条;好处可追踪曾经执行过的命令,作为除错的重要流程,但坏处是若被黑客入侵,可翻阅你曾今执行过的命令;

可对命令或文件名进行补全功能:

  • Tab接在一串指令第一个字的后面,则为命令补全
  • Tab接在一串指令的第二个字以后时,则为文件补齐
  • 若安装bash-completion软件,则在某些指令后面使用tab按键,可进行选项/参数的补齐

支持命令设置别名,如给ls -al命令设置别名lm

1
alias lm='ls -al'

使用前台、后台的控制可让工作进行的更加顺利,以及jobs控制,可随时将任务丢掉后台执行,而不用担心不小心使用了Ctrl+c停掉了该程序,还可以在单一登录环境中达到多任务的目的

Linux可使用shell scripts实现批处理,可将平时管理系统需要下达的连续指令写成一个文件,该文件可通过对话式交互来进行主机的检查工作,也可以藉由shell提供的环境变量及相关指令来进行设计;

bash内建了很多如cdumask等命令,可通过type指令来查看目标指令bash内建指令还是其他非bash所提供的指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type [-tpa] name
选项与参数:不加任何选项与参数时,type会显示出name是外部指令还是bash内建指令
-t:当加入-t参数时,type会将name以底下这些字眼显示出他的意义:
file:表示为外部指令
alias:表示该指令为命令别名所设定的名称
builtin:表示该指令为bash内建的指令功能
-p:若后面接的name为外部指令时,才会显示完整文件名
-a:会由PATH变量定义的路径中,将所有含name的指令都列出来,包含alias

$ 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 内嵌

当指令很长时可使用\[Enter]来换行,注意\Enter键之间没有空格,且在下一行最前面就会主动出现>符号,当下达的指令特别长,或输入了一串错误的指令时,想要快速的将指令整个删除,可使用如下快捷键:

组件键 说明
ctrl+u 从光标处向前删除指令串
ctrl+k 从光标处向后删除指令串
ctrl+a 让光标移动到整个指令串的最前面
ctrl+e 让光标移动到整个指令串的最后面

变量

可使用echo命令来读取变量,变量在被取用时前面必须要加上$或以${变量}的方式来读取;变量的设定与修改直接通过等号=赋值等号两边都不能有空格,若变量内容有空格符可使用单引号双引号将内容包裹起来,双引号内的特殊字符$可保有原本特性单引号内特殊字符仅为一般字符,可使用\回车$空格符单引号变成一般字符变量名称只能是英文字母和数字不能以数字开头bash中当一个变量未被设定时,预设的内容是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
echo $PATH
echo "$PATH"
echo ${PATH}
# 变量赋值
myname=eleven
# 修改变量值
myname=eleven2

# 错误示例
myname = eleven
myname=VBird Tsai
2myname=VBird
name=VBird's name # 单引号与双引号必须要成对
name='VBird's name'

# echo $var:lang is zh_TW.UTF-8
var="lang is $LANG"
# echo $var:lang is $LANG
var='lang is $LANG'

# 正确示例
name="VBird's name"
name=VBird\'s\ name

一串指令的执行中若还需要基于其他指令的执行结果,可使用反单引号包裹指令$(指令);若变量为追加变量时,可用$变量名称${变量}累加内容,若变量需要在其他子程序执行,则需要用export使变量变成环境变量,通常大写字符为系统默认变量;可使用unset取消变量

1
2
3
4
5
6
7
8
9
version=$(ls -ah /home)
version=`ls -ah /home`

# 累加变量内容
PATH="$PATH":/home/bin
PATH=${PATH}:/home/bin

# 取消变量
unset version

可直接在当前shell中执行bash进入到子程序中,可通过exit命令退出子程序;一般父程序自定义变量是无法在子程序内使用的,但可通过export自定义变量变成环境变量后,就能在子程序中使用;

自定义变量环境变量两者区别是环境变量可被子程序使用子程序仅会继承父程序的环境变量;可通过export将自定义变量变成环境变量;可通过declare将环境变量变成自定义变量;环境变量之所以可以被子程序使用,是因为内存配置的原因

  • 当启动一个shell时操作系统会分配一块单独的内存区域,此内存内的变量可被子程序使用
  • 若在父程序使用export时可将自定义变量写到子程序单独的内存区域中
  • 启动子程序离开原本父程序时,子shell可将父shell的环境变量所在的内存区域导入自己的环境变量内存区块中

环境变量

可通过envexport命令来获取当前shell环境下所有环境变量与其内容;bash不仅有环境变量还有自定义变量,可通过set命令即不带任何参数查看所有的变量包括自定义变量

  • HOME:用户家目录
  • SHELL:当前环境使用的shell程序
  • HISTSIZEhistory命令输出记录数,即输出.bash_history文件中最后HISTSIZE
  • HISTFILESIZE.bash_history文件记录命令条数限制,即该文件最多只能存储HISTFILESIZE条历史命令
  • PATH:存储各种工具&命令路径,当使用工具或命令时系统会去PATH中查找对应工具与命令,路径之间用:分隔
  • LANG:Linux系统的语言、地区、字符集的设置,即语系的设置;
  • RANDOM随机数生成器,是随机数文件/dev/random的变量,可通过该变量来获取0~32767之间的随机数,若想生成0~9之间的数值,可通过declare声明数值类型;
1
2
3
4
5
6
7
8
# 生成0~9之间的随机数
declare -i number=$RANDOM*10/32768
echo $number

# 将操作系统设置为英文界面
LANG="en_US.UTF-8"
# 将操作系统设置为中文界面
LANG="zh_CN.UTF-8"
语系变量

Linux中通常仅设定LANG或LC_ALL这两个变量,设置LANGLC_ALL时其他的语系变量就会被这两个变量所取代整体系统默认的语系定义/etc/locale.conf配置文件中;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 查看当前系统中所有可用的语言
locale -a
# 查看语系相关变量及值
locale
LANG=en_US.utf8
LC_CTYPE="en_US.utf8"
LC_NUMERIC="en_US.utf8"
LC_TIME="en_US.utf8"
LC_COLLATE="en_US.utf8"
LC_MONETARY="en_US.utf8"
LC_MESSAGES="en_US.utf8"
LC_PAPER="en_US.utf8"
LC_NAME="en_US.utf8"
LC_ADDRESS="en_US.utf8"
LC_TELEPHONE="en_US.utf8"
LC_MEASUREMENT="en_US.utf8"
LC_IDENTIFICATION="en_US.utf8"
LC_ALL=

cat /etc/locale.conf
LANG="zh_CN.UTF-8"
常见变量说明
1
2
3
4
5
6
7
8
9
10
11
12
BASH=/bin/bash 		# bash的主程序放置路径
BASH_VERSION='4.2.46(1)-release' # bash的版本
COLUMNS=90 # 在目前的终端机环境下,使用的字段有几个字符长度
HISTFILE=/home/dmtsai/.bash_history # 历史命令记录的存储文件
LINES=20 # 目前的终端机下的最大行数
MACHTYPE=x86_64-redhat-linux-gnu # 安装的机器类型
OSTYPE=linux-gnu # 操作系统的类型
PS1='[\u@\h \W]\$ ' # 命令提示字符设置,例:[eleven@eleven ~]$
IFS=$' \t\n' # 预设的分隔符
PS2='> ' # 如果你使用跳脱符号 (\) 第二行以后的提示字符也
$ # 目前这个 shell 所使用的 PID
? # 刚刚执行完指令的回传值

PS1是设置的命令提升符,配置的符号含义如下:

  • \d :显示星期、月、日的日期格式,如:Mon Feb 2
  • \H完整主机名
  • \h主机名第一个小数点之前的名字
  • \t :显示24小时HH:MM:SS格式的时间
  • \T :显示12小时HH:MM:SS格式的时间
  • \A :显示24小时HH:MM格式的时间
  • \@ :显示12小时am/pm格式的时间
  • \u :当前使用者的账号名称
  • \vBASH版本信息,如主机版本为4.2.46(1)-release仅取4.2显示
  • \w完整的工作目录名称由根目录写起的目录名称,家目录会以~取代
  • \W :通过basename函数取得工作目录名称,仅列出最后一个目录名
  • \#执行的第几个指令
  • \$ :提示字符,若为root时提示字符为#,否则为$
特殊变量
  • $:其本身也是一个变量,表示当前shell的线程idPID,可通过echo $$获取
  • ?前一个执行命令返回值0表示执行成功0表示执行过程中发生错误,可通过echo $?获取
read

读取来自键盘输入内容存储到变量中

1
2
3
4
5
6
7
8
9
read [-pt] variable
选项与参数:
-p:后面可接提示字符
-t:后面可接等待的秒数,即等待超时时间

# 将键盘输入内容存储到atest变量中
read atest
# 30秒内输入姓名,将该输入字符串作为名为named的变量值
read -p "Please keyin your name: " -t 30 named
declare和typeset

声明变量类型,若declare未接任何参数列出所有变量,bash对变量有几个基本定义

  • 变量类型默认为字符串,若不指定变量类型,则1+2为一个字符串而不是计算式
  • bash环境中的数值运算,预设最多仅能到达整数,所以1/3结果是0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
declare [-aixr] variable
选项与参数:
-a:将后面名为variable的变量定义成为数组array类型
-i:将后面名为variable的变量定义成为整数数字integer类型
-x:用法与export一样,就是将后面的variable变成环境变量
-r:将变量设定成为readonly类型,该变量不可被修改也不能unset

# 将sum变成环境变量
declare -x sum
# 将sum设置为只读
declare -r sum
# 将sum取消环境变量
declare +x sum
# -p可单独列出变量的类型
declare -p sum
数组变量类型

数组的设定方式:var[index]=content

1
2
3
4
var[1]="small min
var[2]="big min
var[3]="nice min"
echo "${var[1]}, ${var[2]}, ${var[3]}"

ulimit

bash可限制用户使用的系统资源,包括开启文件数量可使用CPU时间可使用内存总量等,想要复原ulimit的设定最简单的方法就是注销再登录,否则就要重新ulimit设定才行;一般用户通过ulimit -f设置文件最大容量时,只能继续减小文件容量不能增加文件容量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ulimit [-SHacdfltu] [配额]
选项与参数:
-H:hard limit严格设定,一定不能超过该设定的数值
-S:soft limit警告设定,可超过该设定值,若超过则有警告信息,通常soft会比hard小
-a:后面不接任何选项与参数,可列出所有的限制额度
-c:当某些程序发生错误时,系统可能会将该程序在内存中的信息写成文件,这种文件就被称为核心文件,此为限制每个核心文件的最大容量
-f:此shell可以建立的最大文件容量,一般可能设定为2GB,位为Kbytes
-d:程序可使用的最大断裂内存(segment)容量;
-l:可用于锁定lock的内存量
-t:可使用的最大CPU时间,单位为秒
-u:单一用户可以使用的最大程序process数量

ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 62113
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 4096
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

# 限制用户仅能建立10MBytes以下的容量的文件
ulimit -f 10240

变量内容删除取代替换

变量设定方式 说明
\${变量#关键词}
\${变量##关键词}
若变量内容从头开始的数据符合关键词,则将符合的最短数据删除
若变量内容从头开始的数据符合关键词,则将符合的最长数据删除
\${变量%关键词}
\${ 变量%%关键词}
若变量内容从尾向前的数据符合关键词,则将符合的最短数据删除
若变量内容从尾向前的数据符合关键词,则将符合的最长数据删除
\${变量/旧字符串/新字符串}
\${变量//旧字符串/新字符串}
若变量内容符合旧字符串则第一个旧字符串会被新字符串取代
若变量内容符合旧字符串则全部的旧字符串会被新字符串取代

#代表从变量内容的最左边开始向右删除,且删除正则表达式匹配的内容,且仅匹配最短的那个##表示符合正则表达式匹配最长的那一个

1
2
3
4
5
6
7
8
# “variable”:变量名称
# “#”:代表从变量内容的最前面开始向右删除,且仅删除最短的那个
# “/*local/bin:”:代表要被删除的部分,配符*来取代0到无穷多个任意字符
${variable#/*local/bin:}
# 仅三处第一个匹配的目录
echo ${PATH#/*:}
# 仅保留最后一个目录
echo ${PATH##/*:}

匹配到的第一内容以及前面的内容都会被删除掉,且注意这里只能使用${}的方式

%只能从后往前匹配

1
2
3
4
# 删除最后面那个目录
echo ${PATH%:*bin}
# 保留第一个目录
echo ${PATH%%:*bin}

字符串取代,可以通过${variable/old/new}表达式替换匹配的第一个内容通过${variable//old/new}表达式替换匹配的所有内容

1
2
# 将PATH的变量内容内的sbin取代成大写SBIN
echo ${PATH/sbin/SBIN}

变量测试与内容替换

变量设定方式 str没有设定 str为空字符串 str已设定非为空字符串
var=${str-expr} var=expr var= var=$str
var=${str:-expr} var=expr var=expr var=$str
var=${str+expr} var= var=expr var=expr
var=${str:+expr} var= var= var=expr
var=${str=expr} str=expr
var=expr
str不变var= str不变var=$str
var=${str:=expr} str=expr
var=expr
str=expr
var=expr
str不变var=$str
var=${str?expr} expr输出至stderr var= var=$str
var=${str:?expr} expr输出至stderr expr输出至stderr var=$str
1
2
3
4
# 若username变量不存在,则给予username内容为root
username=${username-root}
# 若username变量不存在或变量内容为"",则给予username内容为root
username=${username:-root}

alias与history

别名的地定义规则变量的定义规则几乎相同别名='指令 选项...'别名还可以用来取代现有的命令

1
2
3
4
5
6
7
8
9
10
11
# 创建别名
alias lm='ls -al | more'

# 将rm指令替换为rm -i
alias rm='rm -i'

# 查看系统中有哪些别名
alias

# 取消别名
unalias lm

历史命令的读取与记录是这样的:

  • 当以bash登入Linux主机后,系统会主动的由家目录的~/.bash_history读取以前曾经该用户下执行过的指令,~/.bash_history记录数据条数与bash的HISTFILESIZE这个变量设定值有关
  • 若登入主机后共执行100条指令,注销时系统会将101~1100这总共1000笔历史命令更新到~/.bash_history中,即历史命令在注销时,会将最近的HISTFILESIZE笔记录到纪录文件当中
  • 也可用history -w强制立刻写入,之所以是更新因为~/.bash_history记录的条数永远都是HISTFILESIZE条,旧的记录会被主动的替换为最新的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
history [-raw] histfiles
选项与参数:
-n :要列出最近的n笔命令行表
-c :将目前的shell中的所有history内容全部消除
-a :将目前新增的history指令新增入histfiles中,若没有加histfiles则预设写入~/.bash_history
-r :将histfiles的内容读到目前这个shell的history记忆中
-w :将目前的history记忆内容写入histfiles中

# 列出目前内存中所有history记忆的命令
history

echo ${HISTSIZE}

!number
!command
!!
选项与参数:
number :执行第几笔指令的意思
command :由最近的指令向前搜寻指令串开头为 command的那个指令,并执行
!! :执行上一个指令

同一账号同时多次登录的history写入问题,如同时开好几个bash,因为所有的bash都有自己的1000条记录在内存中,等到注销时才会更新到~/.bash_history中,最后注销的bash会最后写入数据,如此其他的bash的指令操作都会被覆盖更新;通过通过值开单个bash,再用工作控制job control来切换不同的工作,这样就能将所有下达过的命令记录,也方便系统管理员进行指令的debug;

历史命令无法记录指令下达时间;可以通过~/.bash_logout来进行history的记录,并加上date来增加时间参数;

bash操作环境

路径与指令搜索顺序

指令执行过程的顺序,可通过type -a 命令命令查询到命令的搜索顺序:

  • 相对/绝对路径执行指令:即在执行命令时,会优先搜索当前所在的目录
  • 由alias找到该指令执行:
  • bash内建的指令来执行
  • 通过$PATH变量顺序搜索到的第一个指令来执行
bash进站欢迎信息

bash进站欢迎信息/etc/issue/etc/motd,除了/etc/issue还有提供给telnet远程登录使用的/etc/issue.net;若想让使用者登入后取得一些信息,可将信息写入/etc/motd文件中;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[eleven@eleven ~]$ cat /etc/issue
\S
Kernel \r on an \m

issue内的各代码意义:
\d 本地端时间的日期;
\l 显示第几个终端机接口;
\m 显示硬件的等级 (i386/i486/i586/i686...);
\n 显示主机的网络名称;
\O 显示 domain name;
\r 操作系统的版本 (相当于 uname -r)
\t 显示本地端时间的时间;
\S 操作系统的名称;
\v 操作系统的版本

登录系统后为什么都没有做,但一进入bash就取得一堆有用的变量,是因为系统有一些环境配置文件,让bash在启动时直接读取这些配置文件,以规划好bash的操作环境,而这些配置文件又分为系统配置文件以及用户配置文件。要注意的是命令别名啦、自定义变量等,在注销bash后就会失效,想要保留设置,需要将这些设定写入配置文件才行;

  • login shell:取得bash时需要完整的登入流程的,如由tty1 ~ tty6登入需要输入用户的账号与密码
  • non-login shell:取得bash接口的方法不需要重复登入的举动

上面两种获取到的bash是不一样的,因为读取的配置文件不一样,login shell只会读取/etc/profile~/.bash_profile~/.bash_login或`~/.profile`两个配置文件;

  • /etc/profile:系统整体的设定,最好不要修改这个文件
  • ~/.bash_profile~/.bash_login~/.profile:属于使用者个人设定,只会读取三个文件其中的一个,首先读取~/.bash_profile,若不存在则读取~/.bash_login,若不存在再读取~/.profile

/etc/profile是一个全局的shelll配置文件,用于设置系统中所有用户的环境变量和初始配置,当用户登录时同首先读取该配置文件,其可以利用使用者的标识符uid决定很多重要变量数据,这也是每个使用者登录取得bash时一定会读取的配置文件,可以通过该配置为所有使用者设定整体环境;该配置文件主要变量:

  • PATH:会依据UID决定PATH变量要不要含有sbin的系统指令目录
  • MAIL:依据账号设定好使用者的mailbox到/var/spool/mail/账号名
  • USER:根据用户的账号设定此一变量内容
  • HOSTNAME:依据主机的hostname指令决定此一变量内容
  • HISTSIZE:历史命令记录笔数,CentOS 7.x设定为1000
  • umask:包括root默认为022而一般用户为002等

/etc/profile配置文件还会执行外部的配置文件,调用顺序如下:

  • /etc/profile.d/*.sh:在/etc/profile.d/目录内且扩展名为.sh,使用者具有r的权限,则该文件就会被/etc/profile执行,CentOS 7.x中该目录下的文件规范了bash操作接口的颜色、语系、ll与ls命令别名、vi命令别名、which命令别名等等,可通过在该目录下创建.sh文件为所有用户创建命令别名
  • /etc/locale.conf:改文件是在/etc/profile.d/lang.sh中被执行,决定bash预设使用的语系;
  • /usr/share/bash-completion/completions/*:命令补齐、文件名补齐、指令的选项/参数补齐功能,就是该目录中找到响应的指令来完成的;该目录内容是由/etc/profile.d/bash_completion.sh 文件载入;

数据重导向

管线命令