shell介绍
shell简介
shell意为层壳,区别于操作系统Kernel(内核),shell是一个命令行解释器,主要用来与用户交互,将用户的命令解释给操作系统,是用户与操作系统内核交互的桥梁,它提供文件操作、进程控制、环境变量设置、管道和重定向等各种功能,因此,Shell 也是许多脚本和自动化任务的基础。此外,shell还有一套自己的编程语法,用于编写shell脚本,shell脚本支持直接调用Linux系统命令
常见的类Unix操作系统中的Shell程序包括:
- sh(Bourne Shell):是Unix系统上最早的shell,由Stephen Bourne于1977年在AT&T贝尔实验室开发。尽管现在有许多更现代的shell可以使用,但许多Unix系统命令和脚本仍然使用sh作为默认解释器
- Bash(Bourne-Again SHell): bash是Bourne Shell的增强版,兼容sh的命令和脚本,并结合了ksh、csh的有用功能。作为GNU项目的一部分,它具有强大的功能和灵活性,支持命令历史、自动补全、脚本编程等功能,是许多Linux系统的默认Shell
- Csh(C Shell):csh是基于C语言语法的shell,它提供了类似于C语言的语法结构和编程特性,由Bill Joy于1978年开发
- Tcsh(TC Shell):Tcsh是C Shell的改进版本,提供了更多的功能和灵活性
- Fish(Friendly Interactive Shell):Fish是一个用户友好的交互式shell软件,具有直观的语法和自动补全功能,还提供了颜色高亮和语法提示等功能,被广泛包含于如 Debian、Ubuntu、Fedora、Gentoo、Arch 等操作系统中
- Ksh(Korn Shell):由贝尔实验室的David Korn在1983年基于Bourne shell的源码开发,它结合了Bourne Shell和C Shell的特性,并引入了许多新的功能
- Zsh(Z Shell):由普林斯顿大学的Paul Falstad开发,它包含了 bash,ksh,tcsh 等其他shell中许多优秀功能,可以作为bash的替代品,它也是macOS 10.15及新版系统的默认shell
在这些众多的shell中,有一些shell软件有类似的语法和特性,由此形成了两大家族:Bourne Shell家族:通常为Linux的默认使用终端,包括sh、ksh、Bash、psh、zsh。以及C Shell家族:包括csh、tcsh,语法与C语言类似,主要用于BSD版Unix系统。
切换shell
查看/etc/shells文件可获取当前Linux支持的shell列表,绝大多数Linux发行版默认使用Bash作为命令解释器,直接输入其他shell名可进行切换,通过exit命令返回默认的Bash终端
父子shell
默认情况下,系统会在用户登录时根据/etc/passwd文件第7字段的设置启动用户的默认shell,这是一个父shell,如果此时输入bash或其他shell的启动命令,会创建一个新的shell程序,这个shell程序是一个子shell。
创建子shell可以保持父子进程环境的纯洁性,创建子shell时,只有部分父进程环境会被复制到子shell中,因此子shell会在一个相对纯洁的环境中执行任务,但这也会造成父shell中的一些变量(如用户自定义的局部变量)无法被子shell中继承使用。在子shell中执行一些独立任务,或者修改一些环境也不会影响到父进程的执行。但是创建子shell成本不菲,它会显著占用一部分系统资源
以下操作会创建一个子shell:
- 通过bash等命令显式调用shell解释器,会启动一个新的子shell
- 通过bash filename.sh或./filename.sh方式执行shell脚本时,会创建一个子shell执行该脚本。相对的,通过source filename.sh或.filename.sh方式执行脚本,会在当前shell中执行而不会创建子shell
- 在命令中使用管道符|时,shell会创建子shell,管道两边的命令都会在单独的子shell中执行。
- 使用反引号` `或$()执行命令替换时,命令会在子shell中执行
- 将命令放到( )中,所有括号中的命令会在子shell中执行,这是最直接创建子shell的方式,通常用来执行多个需要在同一个shell环境中执行的一组命令
- 使用&将任务放到后台执行时,为了不阻塞当前shell执行其他任务,bash会在后台创建子shell执行命令
查看父子shell的创建关系
子shell也可以创建另一个子shell,产生嵌套,通过ps -f命令查看shell程序的PPID来查找其父进程
也可以通过输出环境变量BASH_SUBSHELL来查看当前bash有没有生成子shell,如果该命令返回0,则表明没有子shell,返回1或其他更大的值,就表明有相应数量的子shell。但该命令需要在父shell环境下执行,因此一般在命令分组并形成进程列表时使用
bash shell程序可使用命令行选项修改shell启动方式,以下为bash中可用的命令行参数
参数 | 说明 |
---|---|
-c string | 从string中读取命令并进行处理 |
-i | 启动一个能够接收用户输入的交互shell |
-l | 以登录shell的形式启动 |
-r | 启动一个受限shell,用户会被限制在默认目录中 |
-s | 从标准输入中读取命令 |
父子shell的使用场景
如果命令或脚本需要在一个纯净的隔离环境下执行,或者希望执行某个操作但不改变当前的shell环境,创建子shell十分有用,但需要注意创建子shell带来的资源占用问题
内建命令与外部命令
外部命令
外部命令,又被称为文件系统命令,是存在于bash shell之外的程序。它们并不是shell程序的一部分。外部命令程序通常位于/bin、/usr/bin、/sbin或/usr/sbin中,可以使用which或type -a命令找到命令位置。执行外部命令时,会创建一个该命令对应的子进程,相对于内建命令来说,外部命令需要花费时间和资源来设置新子进程的环境
内建命令
内建命令不需要使用子进程来执行,它们已经和shell编译成了一体,在执行时速度更快,消耗的资源更少。常见的内建命令有cd、echo、pwd、history、alias、continue、exit、jobs、kill等
可以使用type命令来查看某个命令是否为内建命令
有些命令有多种实现方式,如:echo和pwd既有内建命令又有外部命令,默认使用内建命令,如果需要使用外部命令,则指定命令的具体路径即可。可以用type -a命令查看命令的不同实现
命令的执行优先级
- 第一顺位执行通过绝对路径或相对路径执行的命令
- 第二顺位执行自定义的命令别名
- 第三顺位执行bash内部命令
- 第四顺位执行$PATH环境变量定义的第一个命令
命令分组执行
可以在一行中指定多个命令,命令之间使用分号(;)分隔,称为命令列表,这些命令会依次执行
可以使用大括号或小括号将这些命令组合在一起,以便在特定情况(如在if-then语句中)一起执行,这称之为命令分组,主要有两种不同的命令分组:
使用大括号{ }将多个命令组合在一起,可以在当前shell环境中执行,该方法也可用于shell脚本中if-then语句的结构控制
{ pwd ; cd /etc ; ls }如果将命令列表写在括号里,则shell会创建一个子shell执行这些命令,称为进程列表,这样可以使用多个子shell进行多进程处理
创建1个子shell,输出值为1 (pwd ; ls ; cd /etc ; pwd ; ls ; echo $BASH_SUBSHELL) 括号可以嵌套,创建多个子shell进行多进程处理,创建了2个子shell(父shell创建子shell,子shell又创建了它的子shell),输出值为2 ( pwd ; (echo $BASH_SUBSHELL))
创建协程
shell中的协程用于在后台创建一个子shell,并在该shell中执行命令,其实就相当于使用&将命令置入后台模式,但其有点在于我们可以为该协程命名,并且该协程创建时会自动创建一个匿名管道,并将子shell的输入输出重定向到这个管道,实现在 Bash 脚本中并发地执行子进程,并与该子进程进行双向数据通信的功能
协程的创建需要使用命令coproc,其语法为:
coproc 协程名 { 命令1; 命令2; }
如果不指定协程名,则默认为COPROC,之后命令会被置入后台模式,返回后台作业号和进程ID
Bash常用快捷键
快捷键 | 说明 |
---|---|
ctrl+a | 将光标跳转到命令行开头 |
ctrl+e | 将光标跳转到命令行末尾 |
ctrl+左箭头 | 光标向左(前)跳转一个单词,esc+b也可以 |
ctrl+右箭头 | 光标向右(后)跳转一个单词,esc+f也可以 |
ctrl+u | 剪切光标到行首之间的内容(也用来快速删除一整行命令) |
ctrl+k | 剪切光标到行尾之间的内容 |
ctrl+y | 粘贴剪切的内容 |
clear | 清屏 |
ctrl+l | 清屏,同clear命令 |
exit | 退出当前终端 |
ctrl+d | 退出当前终端,同exit命令 |
ctrl+c | 强制终止当前任务 |
ctrl+z | 暂停当前任务并放入后台 |
ctrl+r | 反向搜索执行过的历史命令 |
# | root用户的命令提示符 |
$ | 非root用户的命令提示符 |
> | 次提示符 |
\ | 延续字符 |
Bash的内建命令
history历史命令
在bash中执行过的命令会被缓存在内存中,方便重复调用,当shell退出或用户登出时会被写到用户对应的.bash_history历史命令记录文件中
history [选项] [历史命令保存文件]查看使用过的命令,可以在后面加一个值指定显示n条命令
- -c 清空历史命令
- -w 把缓存的历史命令写入历史命令保存文件~/.bash_history中
- -a 将缓存的历史命令追加到历史命令保存文件中
- -d 值 删除该条历史命令记录
- -s 字符串 将该字符串添加到当前会话的命令历史中,但不执行
调用历史命令
- 可通过上下箭头调用之前执行的命令
- !!再次执行上一次执行的命令
- !n再次执行第n条历史命令(序号以history列出的序号为准)
- !字符串再次执行最后一次以该字符串开头的命令
alias自定义命令名
alias查看所有自定义的命令
alias 别名=’原命令’自定义命令名,原命令依旧可用
使用命令定义的命令名只能临时生效,且只在当前shell中生效,系统重启后将丢失,要永久生效需将自定义的命令添加到文件/用户名/.bashrc中
删除自定义的命令:
unalias 别名
type查询命令类型
查看所指定的命令是shell 内建命令、外部可执行文件,还是其他类型
type [选项] 命令
- -a 查看所有可能的命令类型,有的命令可能同时有shell内建、外部二进制文件、用户别名等多种类型,该选项会列出所有情况
- -t 只显示命令类型,而不显示命令路径,返回类型可能有:lias(别名)、builtin(内建命令)、file(外部命令)、function(定义的函数)、keyword(shell保留的关键字)
- -p (小写)只显示命令路径,如果命令是shell内建命令或别名,使用此选项不会有输出
- -P (大写)在PATH中查找该命令,无论该命令是何种类型,都返回其路径,该选项常被用来在shell脚本中查询并精确执行某命令,如$(type -P curl)
- -f 不在shell函数中查找,即不将命令视为函数来查找
exec命令
exec命令的主要功能有两个
替换当前shell进程
- -c 清除所有环境变量(除了HOME、SHELL、PATH、TERM、MAIL、USER和LOGNAM)
- -l 启动一个新shell执行对应的命令,该shell以登录shell的身份工作
当使用exec命令执行命令或可执行文件时,新执行的程序会替换当前的shell进程。exec命令会将指定的命令或程序加载到当前shell进程的内存空间中,并替换 shell 进程的执行上下文,包括内存代码段、数据段、堆栈中的数据等,同时,新进程会继承原进程的大部分环境和信息,包括进程PID、环境变量、当前工作目录、打开的文件描述符、用户和用户组ID、进程优先级、以及对进程的资源限制(如内存限制、文件大小限制)等信息,这样可以在不创建新进程的情况下,执行新的功能和程序,极大程度上节省了用于创建新进程消耗的系统资源,可以有效减少进程数量并简化进程管理。
该命令可用于在不创建新进程或子shell的情况下切换shell并替换当前的shell进程,通过exec命令切换,Linux会保持shell的PID、用户和用户组ID、当前工作目录等环境不变,也不会创建子shell消耗更多的系统资源
该命令也可以用其他程序替换当前的shell进程,例如,在脚本的最后一行使用 exec 来启动一个长期运行的程序,当脚本即将运行结束,shell的使命也即将完成,通过exec命令让新的程序替换shell进程,而不再需要创建新的进程,可以有效节约系统资源
此外,exec命令还被用于以下场景:
- 当某个进程认为自己不能再为系统和用户作出任何贡献时,就可以调用exec命令让新的进程替代自己,如:一些守护进程,或用于系统初始化启动的进程,当系统完成启动,这些进程的使命已经完成,exec命令可以使新进程接管自己的初始化环境,减少进程间的通信和数据拷贝,提高系统性能
- 在一些特定环境中,使用exec命令可以保证某个关键任务完全接管当前进程,而不会有多余的 shell 进程存在
- 可以用于动态加载新程序和脚本,在其环境不变的情况下执行新脚本,而无需额外创建进程
- 在某些需要严格控制权限的环境中,用 exec 替换当前进程可以确保新的进程继承当前进程的所有权限和环境,使进程在受限的环境中启动并继承了相应权限
修改文件描述符
exec 命令还可以用来重定向文件描述符。在这种用法中,exec 并不会替换当前的 shell 进程,而是会替换脚本的执行环境,该命令可用来重定向整个脚本的标准输入输出文件,或者关闭不需要的文件描述符
环境变量
bash shell使用环境变量(environment variable)来存储有关shell的会话和工作环境,并将这些信息存储在内存中,以便程序或shell脚本能够轻松访问到这些数据,一般情况下,环境变量均使用大写的变量名
全局环境变量
全局环境变量该shell和其创建的子shell中均有效,以下为查询全局环境变量的命令
- printenv查看所有全局环境变量
- -0 每行输出末尾输出空字符(null),而不是换行符,以便其他程序解析输出
- env查看所有全局环境变量
- printenv 变量名查看指定环境变量
- echo $变量名查看指定环境变量
定义全局环境变量需要使用export关键字
- export 变量名=值 定义全局环境变量
- unset 变量名 删除变量
父shell中创建的全局环境变量在子shell中也有效,且如果此时在子shell中修改该变量的值,不会影响其在父shell中的值,修改后的值只在子shell中有效
局部环境变量
局部环境变量只能在定义它们的shell中有效,父shell中的局部环境变量无法在子shell中访问到。Linux中没有只显示局部环境变量的命令,但可以通过set命令查看所有环境变量,包括局部变量、全局变量以及用户定义变量
定义局部环境变量的方法和定义局部自定义变量的方法一样,不需要加export关键字,只需要将变量名大写用于识别该变量是一个环境变量即可
Bash中的环境变量
以下为bash shell种已经定义好的环境变量,可以直接调用,有些环境变量的值为空,因此执行set命令时不一定会列出所有变量
变量名 | 说明 |
---|---|
HOME | 当前用户的主目录 |
UID | 当前用户的真实用户ID(数字形式) |
EUID | 当前用户的有效用户ID(数字形式) |
IFS | shell用来将文本字符串分割成字段的一系列字符 |
当前用户收件箱的文件名 | |
MAILPATH | 冒号分隔的当前用户收件箱的文件名列表 |
OPTARG | getopts命令处理的最后一个选项参数值 |
OPTIND | getopts命令处理的最后一个选项参数的索引号 |
PATH | shell查找命令的目录列表,由冒号分隔 |
PS1 | shell命令行界面的主提示符 |
PS2 | shell命令行界面的次提示符 |
CDPATH | cd命令的搜索路径,切换路径时,cd命令会先尝试在当前目录下查找指定目录并切换过去,如果当前目录下并没有指定的目录,则cd命令会前往该变量设置的路径下查找并切换,可以指定多个路径用冒号分隔 |
BASH | 当前shell实例的全路径名 |
BASH_ALIASES | 含有当前已设置别名的关联数组 |
BASH_ARGC | 含有传入子函数或shell脚本的参数总数的数组变量 |
BASH_ARCV | 含有传入子函数或shell脚本的参数的数组变量 |
BASH_CMDS | 关联数组,包含shell执行过的命令的所在位置 |
BASH_COMMAND | shell正在执行的命令或马上就执行的命令 |
BASH_ENV | 设置了的话,bash脚本会在执行前读取变量中的文件,读取文件中设置的变量,并执行其中的命令 |
BASH_EXECUTION_STRING | 使用bash -c选项传递过来的命令 |
BASH_LINENO | 含有当前执行的shell函数的源代码行号的数组变量 |
BASH_REMATCH | 只读数组,在使用正则表达式的比较运算符=~进行肯定匹配(positive match)时,包含了匹配到的模式和子模式 |
BASH_SOURCE | 含有当前正在执行的shell函数所在源文件名的数组变量 |
BASH_SUBSHELL | 当前子shell环境的嵌套级别(初始值是0) |
BASH_VERSINFO | 含有当前运行的bash shell的主版本号和次版本号的数组变量 |
BASH_VERSION | 当前运行的bash shell的版本号 |
BASH_XTRACEFD | 若设置成了有效的文件描述符(0、1、2),则’set -x’调试选项生成的跟踪输出,可被重定向。通常用来将跟踪输出到一个文件中 |
BASHOPTS | 当前启用的bash shell选项的列表 |
BASHPID | 当前bash进程的PID |
COLUMNS | 当前bash shell实例所用终端的宽度 |
COMP_CWORD | COMP_WORDS变量的索引值,后者含有当前光标的位置 |
COMP_LINE | 当前命令行 |
COMP_POINT | 当前光标位置相对于当前命令起始的索引 |
COMP_KEY | 用来调用shell函数补全功能的最后一个键 |
COMP_TYPE | 一个整数值,表示所尝试的补全类型,用以完成shell函数补全 |
COMP_WORDBREAKS | Readline库中用于单词补全的词分隔字符 |
COMP_WORDS | 含有当前命令行所有单词的数组变量 |
COMPREPLY | 含有由shell函数生成的可能填充代码的数组变量 |
COPROC | 占用未命名的协进程的I/O文件描述符的数组变量 |
DIRSTACK | 含有目录栈当前内容的数组变量 |
EMACS | 设置为’t’时,表明emacs shell缓冲区正在工作,而行编辑功能被禁止 |
ENV | 如果设置了该环境变量,在bash shell运行之前会读取该变量指定路径中的文件,如果未设置,Bash 会默认读取用户的”HOME/.bashrc”文件以设置环境变量,该变量方便用户自定义bash启动时的环境变量(仅用于当bash shell以POSIX模式被调用时) |
FCEDIT | 供fc命令使用的默认编辑器 |
FIGNORE | 在进行文件名补全时可以忽略后缀名列表,由冒号分隔 |
FUNCNAME | 当前执行的shell函数的名称 |
FUNCNEST | 当设置成非零值时,表示所允许的最大函数嵌套级数(一旦超出,当前命令即被终止) |
GLOBIGNORE | 冒号分隔的模式列表,定义了在进行文件名扩展时可以忽略的一组文件名 |
GROUPS | 含有当前用户属组列表的数组变量 |
histchars | 控制历史记录扩展,最多可有3个字符 |
HISTCMD | 当前命令在历史记录中的编号 |
HISTCONTROL | 控制哪些命令留在历史记录列表中 |
HISTFILE | 保存shell历史记录列表的文件名(默认是.bash_history) |
HISTFILESIZE | 最多在历史文件中存多少行 |
HISTTIMEFORMAT | 如果设置了且非空,就用作格式化字符串,以显示bash历史中每条命令的时间戳 |
HISTIGNORE | 由冒号分隔的模式列表,用来决定历史文件中哪些命令会被忽略 |
HISTSIZE | 最多在历史文件中存多少条命令 |
HOSTFILE | shell在补全主机名时读取的文件名称 |
HOSTNAME | 当前主机的名称 |
HOSTTYPE | 当前运行bash shell的机器 |
IGNOREEOF | shell在退出前必须收到连续的EOF字符的数量(如果这个值不存在,默认是1) |
INPUTRC | Readline初始化文件名(默认是.inputrc) |
LANG | shell的语言环境类别 |
LC_ALL | 定义了一个语言环境类别,能够覆盖LANG变量 |
LC_COLLATE | 设置对字符串排序时用的排序规则 |
LC_CTYPE | 决定如何解释出现在文件名扩展和模式匹配中的字符 |
LC_MESSAGES | 在解释前面带有$的双引号字符串时,该环境变量决定了所采用的语言环境设置 |
LC_NUMERIC | 决定着格式化数字时采用的语言环境设置 |
LINENO | 当前执行的脚本的行号 |
LINES | 定义了终端上可见的行数 |
MACHTYPE | 按“CPU-公司-系统”(CPU-company-system)格式定义的系统类型 |
MAPFILE | 一个数组变量,当mapfile命令未指定数组变量作为参数时,它存储了mapfile所读入的文本 |
MAILCHECK | shell查看新邮件的频率(以秒为单位,默认值是60) |
OLDPWD | shell之前的工作目录 |
OPTERR | 设置为1时,bash shell会显示getopts命令产生的错误 |
OSTYPE | 定义了shell所在的操作系统 |
PIPESTATUS | 含有前台进程的退出状态列表的数组变量 |
POSIXLY_CORRECT | 设置了的话,bash会以POSIX模式启动 |
PPID | bash shell父进程的PID |
PROMPT_COMMAND | 设置了的话,在命令行主提示符显示之前会执行这条命令 |
PROMPT_DIRTRIM | 用来定义当启用了\w或\W提示符字符串转义时显示的尾部目录名的数量。被删除的目录名会用一组英文句点替换 |
PS3 | select命令的提示符 |
PS4 | 如果使用了bash的-x选项,在命令行之前显示的提示信息 |
PWD | 当前工作目录 |
RANDOM | 返回一个0~32767的随机数(对其的赋值可作为随机数生成器的种子) |
READLINE_LINE | 当使用bind –x命令时,存储Readline缓冲区的内容 |
READLINE_POINT | 当使用bind –x命令时,表示Readline缓冲区内容插入点的当前位置 |
REPLY | read命令的默认变量 |
SECONDS | 自从shell启动到现在的秒数(对其赋值将会重置计数器) |
SHELL | bash shell的全路径名 |
SHELLOPTS | 已启用bash shell选项列表,列表项之间以冒号分隔 |
SHLVL | shell的层级;每次启动一个新bash shell,该值增加1 |
TIMEFORMAT | 指定了shell的时间显示格式 |
TMOUT | select和read命令在没输入的情况下等待多久(以秒为单位)。默认值为0,表示无限长 |
TMPDIR | 目录名,保存bash shell创建的临时文件 |
环境变量配置文件
用户登录shell时,shell程序会读取环境变量配置文件作为初始化环境,用户可以修改这些文件来指定shell启动时自动执行的任务,以及指定一些环境变量值
全局配置文件
对所有用户生效
- /etc/profile 主要的配置文件,也是用户登录时最先读取的文件,用于保存全局环境变量和shell参数,并调用其他配置文件
- /etc/profile.d/.sh后缀文件 由profile调用
- /etc/bashrc 用于保存bash相关的全局环境变量、函数、命令别名,如PS1等bash终端的环境
/etc/profile配置文件是最主要的配置文件,其中几个常见变量的作用:
变量 | 作用 |
---|---|
USER | 当前用户 |
LOGNAME | 当前用户名,配置文件中LOGNAME=$USER,因此同USER |
用户邮箱 | |
HOSTNAME | 主机名 |
HISTSIZE | 历史命令保存条数 |
umask | 设置文件默认权限 |
用户配置文件
只对单一用户生效,每个用户都有自己的配置文件,root用户位于/root下,普通用户位于/home/用户名下,默认隐藏,需要使用ls -a命令才能看见文件
- $HOME/.bash_profile 用户个人的/etc/profile文件,保存用户自定义的环境变量,该文件会在用户登录时读取,且一般会在文件中调用其他配置文件(如下面的.bashrc)
- $HOME/.bashrc 写有用户自定义的命令别名、函数等,该文件会在打开一个新的交互式bash时都读取一次
- $HOME/.bash_logout 用户注销时执行的环境变量配置文件,可以写一些我们希望系统关机时执行的操作,如备份日志等
- $HOME/.bash_history 记录用户执行过的命令
部分Linux发行版还可能会提供如:$HOME/.bash_login、$HOME/.profile等文件,其作用一般和$HOME/.bash_profile一样,shell一般会优先执行$HOME/.bash_profile
不重启就使配置文件生效
直接执行任一命令
- source 文件名
- .配置文件名
不同启动方式读取的配置文件
日常使用时,启动bash一般分为3种方式,每种方式都会读取不同的配置文件
- 登录用户账户时作为默认启动的交互式shell
- 通过bash等命令切换创建的非登录交互式子shell
- 执行脚本或任务启动的非交互式shell,如:执行脚本、使用at、cron等命令后台执行定时任务、通过脚本或()等方式启动的子shell
作为默认登录shell启动时,bash会依次读取全局/etc/profile文件,以及用户的$HOME/.bash_profile文件,而绝大多数Linux发行版用户$HOME/.bash_profile文件会调用$HOME/.bashrc文件,而$HOME/.bashrc文件又会调用/etc/bashrc文件,因此一般情况下,登录shell会依次加载/etc/profile、$HOME/.bash_profile、$HOME/.bashrc、/etc/bashrc文件
而作为非登录shell启动时,它不会访问/etc/profile、$HOME/.bash_profile等文件,而只会依次读取/etc/bashrc、$HOME/.bashrc文件,$HOME/.bashrc文件虽然包含了对/etc/bashrc的调用,但依旧会首先加载/etc/bashrc文件,$HOME/.bashrc文件对/etc/bashrc的再次调用并不会引起异常。由于非登录shell是从父shell启动的,因此/etc/profile、$HOME/.bash_profile文件中的全局环境变量也会从父shell中继承下来。
作为非交互式shell启动时,bash shell会检查BASH_ENV 环境变量,如果该变量指定了文件,shell会执行该文件里的命令,并读取里面设置的变量。但在绝大多数Linux发行版中,这个变量一般为空,因此非交互式shell一般不会读取配置文件。但该方式启动的shell,若脚本直接将当前shell作为脚本的执行shell,则所有当前shell中已加载的局部变量和全局变量,都可以在脚本中使用。如果脚本中通过诸如进程列表等方式创建了子shell,而其父shell在其加载的/etc/profile、$HOME/.bashrc等(具体加载哪些文件取决于该父shell的启动方式)文件中使用export声明了全局变量,用于执行脚本的子shell会继承这些变量。
环境变量的持久化
通过赋值方式创建的变量保持于内存中,如果退出shell这些变量会失效,可以将这些环境变量写于配置文件中实现变量的持久化。
对于所有用户都可能用到的环境变量,尽量避免将全局的环境变量放在/etc/profile文件中,该文件会在系统升级时被覆盖,可以在/etc/profile.d目录中创建一个.sh结尾的文件,将全局环境变量放在该文件中,然后再在profile文件中加载该目录下的内容。
对于个人用户,可以将环境变量存放于$HOME/.bashrc文件中,但注意,如果需要在脚本中调用某些自定义环境变量,由于脚本启动的一般是非交互式shell,所以需要先检查是否设置了BASH_ENV 环境变量,如果有则需要将环境变量存放在对应的文件中;如果没有,则考虑在其父shell会读取的文件中将环境变量声明为export或在脚本中设置一个新的。
终端控制
ANSI转义字符序列
为了控制终端的显示和行为,ANSI标准定义了一系列用于控制终端输出选项的字符序列,称为ANSI转义字符序列(Escape Sequence)。这些转义字符序列中ASCII码表0到31(0x00–0x1F)之间的字符称为C0控制字符,C0字符通常用于基本的文本控制和终端操作,如:回车(CR)、换行(LF)、制表符(TAB)等。由于原有的控制字符功能不够全面,为了实现更多的终端控制功能,ASCII码表扩展出了值在128-159(0x80–0x9F)之间的C1控制字符,C1控制字符提供了更多扩展的高级控制功能,包括光标控制、颜色设置、字符集切换、设备控制等
CSI序列
C1控制字符中用于控制光标、设置文本样式和颜色以及实现屏幕的清除、局部刷新等操作的控制字符称为CSI(Control Sequence Introducer)字符,CSI字符序列以ESC(\x1b)开头,并紧接一个[(代表CSI),将ESC转换为ASCII码后,控制字符在代码中常写为\e[或\x1b[或\033[,完整的CSI控制语句为:
\e[参数1;参数2;参数3…函数名
- \e[ 为转义字符引导头
- 中间参数部分可以由0个或多个数字组成,多个数字间用分号分隔
- 函数名为需要调用的CSI函数功能名称
函数
CSI提供了很多用于光标控制、屏幕控制、字符渲染的函数
光标控制
函数 | 举例 | 功能 |
---|---|---|
A | \e[nA | 光标上移n行(默认1) |
B | \e[nB | 光标下移n行(默认1) |
C | \e[nC | 光标左移n行(默认1) |
D | \e[nD | 光标右移n行(默认1) |
E | \e[nE | 光标下移n行(默认1),非标准 |
F | \e[nF | 光标上移n行(默认1),非标准 |
G | \e[nG | 光标移动至当前行的n列,非标准 |
H | \e[x;yH | 光标移动到x行y列 |
s | \e[s | 保存光标位置 |
u | \e[u | 取出光标位置 |
l | \e[?25l | 隐藏光标 |
h | \e[?25h | 显示光标 |
屏幕控制
函数 | 举例 | 功能 |
---|---|---|
J | \e[nJ | 清除指定范围内的屏幕,0为光标位置到屏幕末尾,1为光标位置到屏幕开头,2为全屏幕 |
K | \e[nK | 清除该行的指定范围,0为光标到行尾,1为光标至行头,2为整行 |
S | \e[nS | 整页向上滚动n行,非标准 |
T | \e[nT | 整页向下滚动n行,非标准 |
修改字体样式
终端中最为常用的转义序列还是用于修改终端字体颜色、背景颜色和显示效果的函数和参数,即SGR(Set graphics mode)函数m,常用于配合echo语句输出不同样式的提示信息,修改终端样式的转义字符序列一般为:
\e[参数m
在指定多个参数时,转义字符序列通常为:
\e[字体样式;字体颜色;背景色m
参数不分先后顺序,指定多个同类样式时,后面的样式将覆盖前面的样式(如:指定多个背景色,只有最后一个背景色会生效)
恢复默认
\e[0m表示取消所有设置的属性(包括颜色、加粗、闪烁等效果),使后续输出内容恢复默认属性
文字效果
设置效果代码 | 效果 | 取消效果代码 |
---|---|---|
1 | 加粗高亮 | 21 |
2 | 变暗(未被广泛支持) | |
3 | 斜体(未被广泛支持) | |
4 | 下划线 | 24 |
5 | 缓慢闪烁,低于每分钟150次 | 25 |
6 | 快速闪烁,每分钟150次以上(未被广泛支持) | |
7 | 反转背景色和前景色 | 27 |
8 | 隐藏,常用于密码(未被广泛支持) | 28 |
9 | 划掉文字(未被广泛支持) | |
10 | 默认字体 | |
11-19 | 替代字体 | |
20 | 尖角体 |
字体颜色(前景色)
以下颜色在不同终端中被定义了不同的RGB值,且没有一个通用标准,因此不同终端中的样式可能有所不同,详情可参考:ANSI escape code
代码 | 颜色 | 代码 | 颜色 |
---|---|---|---|
39 | 默认颜色 | ||
30 | 黑色 | 90 | 灰色 |
31 | 红色 | 91 | 亮红色 |
32 | 绿色 | 92 | 亮绿色 |
33 | 黄色 | 93 | 亮黄色 |
34 | 蓝色 | 94 | 亮蓝色 |
35 | 品红 | 95 | 亮品红色 |
36 | 青色 | 96 | 亮青色 |
37 | 白色 | 97 | 亮白色 |
字体背景色
每一个颜色都和上面相同,只是他们将作为背景色使用
代码 | 颜色 | 代码 | 颜色 |
---|---|---|---|
49 | 默认颜色 | ||
40 | 黑色 | 100 | 灰色 |
41 | 红色 | 101 | 亮红色 |
42 | 绿色 | 102 | 亮绿色 |
43 | 黄色 | 103 | 亮黄色 |
44 | 蓝色 | 104 | 亮蓝色 |
45 | 品红 | 105 | 亮品红色 |
46 | 青色 | 106 | 亮青色 |
47 | 白色 | 107 | 亮白色 |
修改文件目录颜色
想在使用ls命令后使各种不同类型文件显示不同颜色,参考dircolors命令
OSC序列
OSC(Operating System Command)是一类用于执行操作系统级别命令的控制序列,通常用来设置终端标题,或与系统剪贴板进行交互等。OSC和CSI都是ANSI转义序列的一部分,其中CSI是C1控制字符集的一部分,而OSC是独立的多字符集合,不属于C1控制字符集
与CSI字符序列语法类似,OSC序列也是以ESC开头,随后,OSC序列后紧接一个],后接一个数字标识符,然后是一个命令或参数,最后以\a(BEL)或\e\(ESC+\)结尾,完整的OSC语法为:
\e]函数标识值;参数1;参数2;参数3…\a
- \e]是OSC控制序列的引导头
- 函数值功能参考下表
- \a 标识控制序列结束
OSC函数值
OSC字符序列也可以修改终端的文本颜色,背景色等样式,与CSI不同的是,OSC字符序列控制的是全局的终端样式,而CSI序列只修改跟随在CSI后的文本样式,而不会修改已经输出完毕的文本样式。但OSC在一些终端中(尤其远程终端中)只支持修改终端窗口的标题等,而不支持修改光标、文本颜色等样式
值 | 说明 |
---|---|
0 | 更改窗口标题和标签栏标题 |
1 | 设置终端窗口的图标名 |
2 | 仅更改窗口标题 |
10 | 更改字体颜色(前景色) |
11 | 更改背景色 |
12 | 更改光标颜色 |
52 | 将文本写入剪贴板 |
104 | 重置所有颜色 |
修改命令提示符
默认情况下的命令提示符通常为[用户名@主机名 当前目录]主提示符(不同Linux发行版可能有所不同),如:[root@localhost etc]#,该默认值由系统定义的变量PS1决定,可通过echo $PS1 查看当前配置,也可以通过修改PS1变量自定义命令提示符,如:
通过修改PS1变量自定义的命令提示符,只在本次登录有效,系统重启后将失效,需要永久修改命令提示符,可以将上述语句写入配置文件/etc/bashrc中,若只想修改某个用户的终端,可以修改该用户家目录下的.bashrc配置文件中的PS1变量,修改完后可以使用source 文件名命令重新加载配置文件,使配置立即生效
修改命令提示符文本颜色
修改命令提示符文本颜色时,PS1变量值中的CSI语法需要使用[ ]进行包裹,并且”[ ]”需要使用转义字符进行转义,避免bash解析其中的语法,即语法为:
\[\e[颜色码m\]
以下是自定义命令提示符时常用的转义字符:
转义符 | 作用 |
---|---|
\u | 显示当前用户名 |
\h | 显示简写主机名(不包含域名,该值来自/etc/hostname文件) |
\H | 显示完整的主机名(通常包括域名) |
\w | 显示当前所在路径的完整名称 |
\W | 显示当前所在目录(路径的最后一个路径段) |
\# | 显示本次会话已经执行的命令个数 |
\$ | 显示命令主提示符,root用户提示符为”#”,普通用户为”$” |
\d | 显示当前系统日期,格式为”星期 月 日” |
\t | 显示24小时制时间,格式为”HH:MM:SS” |
\T | 显示12小时制时间,格式为”HH:MM:SS” |
\A | 显示24小时制时间,格式为”HH:MM” |
\n | 换行符 |
\[ 和 \] | 用于定义非打印字符的开始和结束(主要用于设置颜色,避免光标定位错误) |
终端标题
虽然PS1主要用于控制终端的命令提示符,但我们可以在PS1中嵌入OSC转义序列来控制终端的标题。与CSI类似,PS1变量值中的OSC序列也需要使用[ ]进行包裹,并且”[ ]”需要使用转义字符进行转义,避免bash解析其中的语法
本地终端登录前的欢迎信息
需要修改本地终端登录时显示的信息,可修改配置文件/etc/issue,配置文件中可使用的转义字符:
转义符 | 作用 |
---|---|
\d | 显示当前系统日期 |
\t | 显示当前系统时间 |
\s | 显示操作系统名称 |
\l | 显示登录的终端号 |
\m | 显示硬件架构信息,如i386等 |
\n | 显示主机名 |
\o | 显示域名 |
\r | 显示内核版本 |
\u | 显示当前用户登录的序列号 |
远程终端登录前的欢迎信息
修改远程终端登录时显示的信息,可修改配置文件/etc/issue.net,该文件不支持上述本地终端登录可用的转义字符,需要显示此欢迎信息,需要在配置文件/etc/ssh/sshd_config中加入Banner /etc/issue.net配置内容(需要重启生效)
# no default banner path
#Banner none
Banner /etc/issue.net
# Accept locale-related environment variables
终端登录成功后的提示信息
配置文件位于/etc/motd(Message of the Day文件),用于在远程和本地终端登录成功后显示提示信息,文件通常默认为空
在ubuntu中,登录提示文本MOTD通常是动态生成的,配置文件位于/etc/update-motd.d/中,包含一系列脚本,这些脚本会生成系统信息(如负载、内存使用等)并在登录时显示,脚本名前的数字表示脚本执行顺序,相同数字的脚本会按照脚本名字母顺序依次执行,执行顺序也决定了这些文本的显示顺序
#!/bin/sh
echo "欢迎使用shiwivi的 Ubuntu 服务器!"
3. 给脚本添加可执行权限
sudo chmod +x /etc/update-motd.d/99-custom
如果不想显示某个脚本的内容,可以通过修改文件名(例如在文件名中添加 .disabled)来禁用特定脚本,或者修改脚本中的输出语句