shell作为操作系统的外壳,是linux内核与用户交互的桥梁。 用户通过shell提供的命令行界面执行命令。 用户以命令行模式登陆后看到的界面即为shell提供的交互界面,如下所示:

                Ubuntu 12.04.1 LTS initroot tty1
 
                initroot login: root
                Password: 

                Last failed login: Mon Dec 30 20:39:54 CST 2019 from 168.181.11.252 on ssh:notty
                There were 14 failed login attempts since the last successful login.
                Last login: Fri Dec 27 11:38:54 2019 from 61.177.142.236

                Welcome to Alibaba Cloud Elastic Compute Service !

                [root@initroot ~]#
            

广义的shell分为两大类:
1.图形界面shell(Graphical User Interface shell 即 GUI shell)
例如windows下的Windows Explorer,linux下的X window manager(BlackBox和FluxBox),以及功能强大的CDE、GNOME、KDE、 XFCE等。
Linux提供了像Microsoft Windows那样的可视的命令输入界面--X Window的图形用户界面(GUI)。 它提供了很多桌面环境系统,其操作就像Windows一样,有窗口、图标和菜单,所有的管理都是通过鼠标控制。
2.命令行式shell(Command Line Interface shell ,即CLI shell)
平时提到的shell就是命令行shell。shell作为linux的命令解释器,管理用户与内核之间的交互, 等待并接收用户输入命令,解释用户输入并调用相应的应用程序,处理各种各样的输出结果。
Linux用户可以定制自己喜欢的用户界面或Shell,用以满足个性化需要。

shell

回到顶部

Shell提供了用户与内核进行交互的接口,实际上是一个命令解释器,它解释用户输入的命令并传递给内核执行。
Shell有自己的编程语言,它允许用户编写由shell命令组成的程序。
Shell编程语言具有普通编程语言的特点,比如循环结构和分支控制结构等。
shell可以分为交互式shell和非交互式shell,登录shell和非登录shell. 系统启动的时候由login程序加载执行的shell就是登录交互式shell,也就是用户工作的界面.
交互式shell等待用户输入并执行用户输入的命令,只有当用户退出,shell才会终止。
非交互式shell不与用户交互,而是读取并执行存放在shell script文件中的命令,当读到文件的结尾,shell也就终止了。
shell script文件存放命令的集合,可以被重复执行。

Shell语法分析命令列,将命令分解成以空白区分开的符号(token),在此空白包括了跳位键(tab)、空格和换行(New Line)。 如果这些字包含了metacharacter,shell 将会评估(evaluate)它们的正确用法。 另外,shell 还管理文件输入输出及幕后处理(background processing)。在处理命令列之后,shell 会寻找命令并开始执行它们。
Shell 的另一个重要功用是提供个人化的用户环境,这通常在shell的初始化文件中完成(.profile、.login、.cshrc、.tcshrc等等)。 这些文件包括了设定终端机键盘和定义窗口的特征;设定变量,定义搜寻路径、权限、提示符号和终端机类型; 以及设定特殊应用程序所需要的变量,例如窗口、文字处理程序、及程序语言的链接库。 Korn shell 和 C shell 加强了个别化的能力:增加历程、别名、和内建变量集以避免用户误杀文件、不慎签出、并在当工作完成时通知用户。 Shell也能当解译性的程序语言(interpreted programing language)。
Shell程序,通常叫做命令文件,它由列在文件内的命令所构成。 此程序在编辑器中编辑(虽然也可以直接在命令列下写作程序,online scripting),由UNIX命令和基本的程序结构,例如变量的指定、测试条件、和循环所构成。 您不需要编译 shell 命令档。Shell 本身会解译命令档中的每一行,就如同由键盘输入一样。 shell负责解译命令,而用户则必须了解这些命令能做什么。下面列出了一些有用的命令和它们的使用方法。

shell作为Linux操作系统的外壳,为用户提供使用操作系统的接口。它是命令语言、命令解释程序及程序设计语言的统称。
shell是用户和Linux内核之间的接口程序,如果把Linux内核想象成一个球体的中心,shell就是围绕内核的外层。
当从shell或其他程序向Linux传递命令时,内核会做出相应的反应。
shell是一个命令语言解释器,它拥有自己内建的shell命令集,shell也能被系统中其他应用程序所调用。用户在提示符下输入的命令都由shell先解释然后传给Linux核心。 有一些命令,比如改变工作目录命令cd,是包含在shell内部的。还有一些命令,例如拷贝命令cp和移动命令mv,是存在于文件系统中某个目录下的单独的程序。
对用户而言,不必关心一个命令是建立在shell内部还是一个单独的程序。
shell首先检查命令是否是内部命令,若不是再检查是否是一个应用程序 (这里的应用程序可以是Linux本身的实用程序,如ls和rm,也可以是购买的商业程序,如xv,或者是自由软件,如emacs)。
然后shell在搜索路径里寻找这些应用程序。
如果键入的命令不是一个内部命令并且在路径里没有找到这个可执行文件,将会显示一条错误信息。
如果能够成功找到命令,该内部命令或应用程序将被分解为系统调用并传给Linux内核。
shell的另一个重要特性是它自身就是一个解释型的程序设计语言, shell程序设计语言支持绝大多数在高级语言中能见到的程序元素,如函数、变量、数组和程序控制结构。
shell编程语言简单易学,任何在提示符中能键入的命令都能放到一个可执行的shell程序中。
当普通用户成功登录,系统将执行一个称为shell的程序。正是shell进程提供了命令行提示符。
默认情况下,对普通用户用“$”作提示符,对超级用户(root)用“#”作提示符。 一旦出现了shell提示符,就可以键入命令及命令所需要的参数。shell将执行这些命令。
如果一条命令花费了很长的时间来运行,或者在屏幕上产生了大量的输出,可以从键盘上按ctrl+c发出中断信号来中断它。
当用户准备结束登录对话进程时,可以键入logout命令、exit命令或文件结束符(EOF)(按ctrl+d实现),结束登录。
我们来实习一下shell是如何工作的。

              peter@www:~$ make work
              make: *** No rule to make target 'work'. Stop.
              peter@www:~$ 
            
make是系统中一个命令的名字,后面跟着命令参数。在接收到这个命令后,shell便执行它。本例中,由于输入的命令参数不正确,系统返回信息后停止该命令的执行。
shell会寻找名为make的程序,并以work为参数执行它。make是一个经常被用来编译程序的程序,它以参数作为目标来进行编译。
在“make work”中,make编译的目标是work。因为make找不到以work为名字的目标,它便给出错误信息表示运行失败,用户又回到命令提示符下。
用户键入命令后,shell如果找不到以命令名为名的程序文件,就会给出错误信息。例如,如果用户键入:
              peter@www:~$ myprog
              bash:myprog:command not found
              peter@www:~$ 
            
可以看到,用户得到了一个没有找到该命令的错误信息。用户敲错命令后,系统一般会给出这样的错误信息。

shell相比图形界面的优势

回到顶部

通过X Window或者Web接口的图形界面管理工具可以非常快速方便的对linux进行设置, 例如Webmin就是一款非常好用的基于web的linux系统设置工具,甚至一些非常复杂的设置都可以轻松搞定! 但是我们为什么还要用命令行的shell呢?
虽然X Window 与 web 接口的工具简单易用功能强大,但毕竟他是将所有利用到的软件都整合在一起的一组应用程序而已, 并非是一个完整的套件,所以某些时候当用户升级或者是使用其他套件管理模块 (例如 tarball 而非 rpm 文件等等) 时,就会造成设定的困扰了。 甚至不同的 distribution 所设计的 X window 接口也都不相同,这样也造成学习方面的困扰。
文字接口的shell就不同了!几乎所有的linux发行版distributions使用的bash都是一样的! 这样掌握了bash的操作技能就可以在各个linux发行版之间无缝切换了,正所谓一法通、万法通!
远程管理:文字接口就是比较快!
我们经常需要远程连接linux服务器,虽然已经出现了非常多的图形界面远程连接工具,但是毕竟文字的传输速度又要远远大于图形的传输。 所以使用文件接口的shell在远程连接的时候操作更加快速稳定。
学习文字接口的shell也可以更深入的了解linux,可以接触到更多深层次原理性的东西。而如果只是用图形界面的话,除了只会动动鼠标, 看上去简单又轻松,但是真的是错了深入学习linux的大好机会了。
Linux 的任督二脉: shell 是也!
在linux主机运维工作中,编写shell脚本是每个系统管理员的基本功,例如系统管理员经常要做的一件工作就是查看系统的日志文件, 如果管理的主机非常多,每个主机都要花上几十分钟来查阅日志文件!那真的是效率太低下了。 这时候用shell提供的数据流重导向以及管道命令,只需要花费不到十分钟就可以看完所有linux主机的日志文件信息了!
作为linux系统管理员,shell和shell scripts是必须要认真掌握的必备技能!

1.shell的不同版本简介

回到顶部

shell有多种不同的版本,主要有下列几种版本:

Bourne Shell: 贝尔实验室开发;
BASH: GNU的Bourne Again Shell,是GNU操作系统上的默认shell;
Korn Shell: 由Bourne SHell发展而来,基本与Bourne Shell兼容;
C Shell: SUN公司BSD版本的Shell;
Z Shell: The last shell you'll ever need! Z是最后一个字母,也就是终极Shell。它集成了bash、ksh的重要特性,同时又增加了自己独有的特性。

早期Unix年代,开发者众多,不同的开发者开发了不同版本的shell,例如常听到的Bourne SHell(sh)、 在Sun里头默认的C SHell、商业上常用的K SHell, 还有TCSH等,每一种Shell都有自己的特点。
目录大多数linux发行版默认的shell是bash,bash为Bourne Again Shell的简写,意为Bourne Shell的增强版本, 也是基于GNU的架构开发出来的!
第一个流行的shell是由Steven Bourne开发出来的,为了纪念他所以就称为Bourne shell,简称为sh!
另一个广为流行的shell为c shell,c shell是由柏克莱大学的Bill Joy为BSD版的Unix系统设计的shell, c shell的语法有点类似C语言,所以才得名为C shell ,简称为csh!由于在学术界Sun主机势力相当的庞大, 而Sun主要是BSD的分支之一,所以C shell也是另一个很重要而且流传很广的shell。
由于Linux为C程序语言撰写的,很多程序设计师使用C来开发软件,因此C shell相对的就很热门了。 Sun公司的创始人就是 Bill Joy,而BSD最早就是Bill Joy开发出来的。
由于linux操作系统有各种不同的发行版,每种发行版都可能有不同的shell。 实际上shell只是linux命令解释器的一个统称,就像一个人可以穿不同的衣服一样,shell作为linux操作系统的外壳,也可以有很多种不同版本的shell。 常用的shell有Korn Shell、Bourne shell、C shell、Bourne Again shell。 目前大部分linux发行版默认的shell就是Bourne Again shell,简称bash。

2.查看系统的合法shell

回到顶部

/etc/shells文件记录了当前系统登录用户可以使用的shell:

              [root@initroot ~]$ cat /etc/shells
              # /etc/shells: valid login shells
              /bin/sh
              /bin/bash
              /bin/rbash
              /bin/dash
            
系统某些服务在运行过程中可能会通过/etc/shells文件检查登录用户能够使用的shell!
举例来说,某些FTP网站会去检查使用者的可用shell,而如果你不想要让这些用户使用FTP以外的主机资源时, 就会给予该登录用户一些特别的shell,让使用者无法以其他服务登入主机。这个时候,你就得将那些特殊的shell写到/etc/shells当中了。 举例来说,我们的CentOS 7.x 的/etc/shells里头就有个/sbin/nologin文件的存在,这个就是我们说的怪怪的shell
/etc/passwd文件中每一行的最后一个字段记录了登录用户默认取得的shell, 当登录用户登录系统后,就会查询该文件来获取登录用户的shell,passwd文件内容如下所示:
              [peter@study ~]$ cat /etc/passwd
              root:x:0:0:root:/root:/bin/bash
              daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
              bin:x:2:2:bin:/bin:/usr/sbin/nologin
              sys:x:3:3:sys:/dev:/usr/sbin/nologin
              sync:x:4:65534:sync:/bin:/bin/sync
              games:x:5:60:games:/usr/games:/usr/sbin/nologin
              ...
            
每一行的最后一个字段就是登录用户默认的登录shell了。例如root用户的登录shell是/bin/bash, 注意有些账号例如bin与daemon等,他们的登录shell是/usr/sbin/nologin。表示该用户登录后无法取得shell。
/etc/shells文件列出了当前系统可用的所有登录shell,通过cat命令查看该文件的内容如下:
              peter@initroot:/home/wwwroot/default/kblog$ cat -n /etc/shells
              1 # /etc/shells: valid login shells
              2 /bin/sh
              3 /bin/bash
              4 /bin/rbash
              5 /bin/dash
            
加上-n选项可以输出每行的行号. 更多关于cat命令的内容可参考:cat和tac

3.查看当前系统运行的shell

回到顶部

如何查看当前系统用的是哪个版本的shell呢?只需要通过查看SHELL环境变量就可以看到当前登录用户用的是哪个shell了。
echo命令可以显示变量的值,在shell命令行输入如下命令:

              peter@peter-VirtualBox:~$ echo $SHELL
              /bin/bash
            
输出的是一个文件的绝对路径名,文件名为/bin/bash。怎么shell是一个文件吗? 我们知道linux命令是计算机软件程序,绝大多数软件程序都是以可执行文件的形式存放在磁盘的某个目录中, 而这些可执行文件大部分都是由c/c++语言或者汇编语言编写的程序代码通过编译链接而来。 shell自然也不例外,也是由c语言编写的计算机程序,所以这里的/bin/bash其实就是bash的可执行程序文件了。 可以看到当前系统运行的shell就是bash shell,如果不加特别说明,我们提到的shell,默认就是指bash shell。

更多关于echo命令的内容可参考:echo命令
更多关于shell变量和环境变量的内容可参考:linux shell变量与环境变量
更多关于计算机程序和c/c++语言编程以及编译链接的知识可以参考:
c/c++程序的基本概念
c/c++程序设计
linux高级编程

也可以通过查看/etc/passwd文件每一行的最后字段查看每个用户登录后运行的是哪个shell:

              peter@initroot:~$ cat -n /etc/passwd
              1 root:x:0:0:root:/root:/bin/bash
              ...
              41 peter:x:1000:1000:peter,,,:/home/peter:/bin/bash
              43 test:x:1001:1001::/home/test:/bin/sh
            
关于/etc/passwd文件的详细说明参考: linux用户管理

关于shell的更多内容参考:
什么是linux shell
认识bash shell

开机登陆进入linux命令行后,进行的所有操作都和bash有关。 我们经常说linux命令行模式,其实准确的说法应该是shell命令行,因为linux的命令行环境就是由bash shell提供的。 虽然目前的shell有很多版本,但是目前主流的shell就是bash了。我们提到shell的时候,默认指的就是bash。
bash所涉及的内容非常的多,包括变量的设置与使用、bash操作环境的设置、数据流重导向、管道命令等! bash是命令行模式(command line)下主机维护与管理的重要基础!
管理整个计算机硬件的其实是操作系统的内核kernel,linux操作系统内核也是一种软件,linux内核控制计算机硬件资源,提供程序运行环境。 linux内核向应用程序提供服务。linux内核提供的服务典型的有执行新程序、打开文件、读文件、分配存储区、获得当前时间等等。
出于安全以及易用性的考虑,linux并不希望用户直接和内核打交道,而是通过shell和内核沟通。

Shell和linux内核的关系

回到顶部

我们刚才提到linux内核为用户应用程序(或进程)提供各种服务, 这些服务包括进程管理、内存管理、文件系统管理、设备管理、网络管理、时间管理等。 linux内核向外提供服务的唯一接口就是系统调用(system call)。应用程序(或进程)通过linux系统调用获取这些基础服务, 而用户的特定业务逻辑则由用户应用程序(或进程)实现。
例如我们前面接触的chmod, chown, vi, fdisk, mkfs等命令都是应用程序,包括提供网络服务的应用程序ssh、nginx、vsftp等等。
这些应用程序都是通过系统调用与linux内核打交道。这些应用程序都是由程序开发工程师编写,有他们规划应用程序应该用到哪些linux系统调用 如果你对这方面的内容感兴趣,可以参考linux环境高级编程部分链接。对linux内核感兴趣的话可以参考我们的linux内核部分链接。
我们前面学习的各种命令包括 man, chmod, chown, vi, fdisk, mkfs 等等命令,这些指 令都是独立的应用程序, 但是我们可以透过shell来执行操作这些应用程序,让这些应用程序调用内核来完成所需的工作!
而如果你学习linux的方向是服务器运维或者就是单纯的喜欢使用linux,那么主要的工作其实就是使用这些由程序开发工程师编写的应用程序了。 我们之前执行的那个命令,其实就是用的程序开发工程师编写的命令应用程序啦! 那么这些应用程序怎么使用呢?也就是我我们怎么执行命令呢?总得有一个应用环境吧,这就是shell提供的命令行环境了。我们前面执行的命令也就是在shell命令行下执行的。 其实shell也是一个应用程序,也是通过linux系统调用和linux内核打交道。 shell的主要作用就是为用户执行命令(使用应用程序)提供操作环境,所以shell也叫命令解释器。解释用户输入,然后执行用户输入的命令 unix系统体系结构 公用函数库构建在系统调用接口之上,上层应用程序既可以使用公用函数库,也可以使用系统调用。 shell是一个特殊的应用程序,为运行其他应用程序提供操作接口。
从广义上说,操作系统包括了内核和一些其他软件,这些软件包括系统实用程序(system utility)、应用程序、shell以及公用函数库。 这些软件使得计算机能够发挥作用,并使计算机具有自己的特性。
而我们其实也就是通过应用程序来和内核进行沟通
真正能和linux内核打交道的是研发工程师。
应用程序在最外层,就如同鸡蛋的外壳一样,因此这个咚咚也就被称呼为shell!
其实shell的功能只是提供用户操作系统的一个接口,因此这个shell需要可以调用其他软件才好。
也就是说,只要能够操作应用程序的接口都能够称为shell。 狭义的shell指的是指令列方面的软件,包括本章要介绍的 bash 等。 广义的shell则包括图形接口的软件!因为图形接口其实也能够操作各种应用程序来调用内核工作! 不过在本章中,我们主要还是在使用 bash 啦!

bash(/bin/bash)是GNU计划中重要的工具软件之一,目前也是Linux distributions的标准shell。 bash为Bourne Again Shell的简写,为Bourne shell的增强版本。 既然bash能作为绝大部分linux发行版的默认shell,那么就一定有一些优点。bash主要的优点有以下几个:
1.history历史命令记忆功能
bash可以记忆使用过的命令!只需要在命令行按上下键就可以前后浏览执行过的命令! bash会将用户执行过的命令记录在登录用户家目录下的.bash_history文件中, 不过.bash_history文件中记录的是登录用户上一次登录之前的历史命令。而本次登录所执行的命令会先暂时存放在内存中,用户退出本次登录后, 会将本次登录所执行的命令全部追加到.bash_history文件中。 大多数linux distribution默认可以记录1000个历史命令。
有时候系统中出现了一些预期之外的结果,就可以用bash的历史记录功能查询一下曾经执行过哪些命令, 或者我们可能会重复执行一个刚刚执行过的命令,就可以用命令历史记录非常快速的找到刚刚输入的命令,大大提高的工作效率。 当然shell的历史记录功能也可能会带来麻烦,例如可能会被黑客利用。
2.tab键命令与文件补全功能
在bash命令行下输入命令的时候,时不时的按下tab键是个很好的习惯!tab键可以快速补全你想要输入的命令或者命令选项参数。
bash的补全功能可以让用户快速输入字符,并且可以帮助用户检查拼写错误。例如使用cat命令查看文件/etc/passwd. 在命令行输入ca,但是突然忘记了cat命令怎么拼写了,此时就可以按两下tab键,bash会把所有以ca开头的命令列出来, 我们就可以查看bash给出的命令列表,帮助我们想起原来要输入的命令是cat。
当然这里cat命令拼写比较简单,在你遇到记忆不清的命令的时候可以尝试使用tab帮助恢复记忆。 接着刚才的输入cat /e,此时再次按两下tab键,bash就会找到/目录下所有以e开头的文件或目录,因为根目录下以e开头的文件或者目录只有/etc, 所有bash就会自动把参数补全为cat /etc/,接着输入cat /etc/pasw, 此时再次按两下tab键,没有任何反应,怎么回事儿呢?如果你多次按下tab键都没有任何反应,说明你的输入可能是有问题的, 因为还是跟刚才同样的道理,bash会在/etc目录下寻找以pasw开头的文件或者目录,但是bash并没有找到,所以就不会有任何反应了。 这时候我们就应该很快意识到是不是自己的输入有问题呢,检查发现确实是拼写错了,应该是cat /etc/pass,此时再次按两下tab键, bash就会帮我们把命令补全为cat /etc/passwd了,这就是tab键的快速补全与拼写检查功能了。
使用tab键的时机依据tab接在命令后或参数后而有所不同。我们再复习一次:
Tab接在一串命令的第一个字的后面,则为命令补全;
Tab接在一串命令的第二个字以后时,则为文件补齐;
若安装bash-completion软件,则在某些命令后面使用tab按键时,可以进行选项/参数的补齐功能! 所以说,如果我想要知道环境当中所有以c为开头的命令,就按下c[tab][tab],bash就会把所有以c开头的命令全部列出来!
3.alias命令别名设置功能
linux命令别名 可以给一个命令起一个别名绰号,例如我们经常用ls -alF命令查看当前目录下所有文件的详细信息,但是我太懒了,真的不想输入太多的字符, 这时候就可以给这么一串命令起一个别名,比如ll,这样在命令行下执行ll就相当于执行ls -alF命令了。 使用alias命令就可以给一个命令起别名。上面我们给ls -alF起别名绰号为ll,就可以这样:

              [root@initroot ~]$ alias ll='ls -alF'
            
也可以直接使用alias命令不加任何参数,alias会列出当前系统中所有的命令别名:
              [root@initroot ~]$ alias
              alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
              alias egrep='egrep --color=auto'
              alias fgrep='fgrep --color=auto'
              alias grep='grep --color=auto'
              alias l='ls -CF'
              alias la='ls -A'
              alias ll='ls -alF'
              alias ls='ls --color=auto'
            
4.bash作业管理机制
bash将工作环境分为前台foreground和后台background,bash把通过它启动的命令进程称为作业(job), bash可以将作业放到前台执行,也可以将作业放在后台执行或者暂停。后台运行的作业不会被Ctrl+c组合键强制退出。 通过作业管理(job control),可以在bash提供的单一命令行环境下同时运行多个作业。
5.程序脚本shell scripts
shell scripts类似DOS中的批处理文件,将一堆命令写在一个文件中。不过Linux下的shell scripts具有更强大的功能。 bash有自己的变量、条件分支语句、判断语句、循环语句,具备完善的程序设计功能, shell scripts可以被认为是一门程序设计语言。 在linux运维工作中,系统管理员为了完成某一项特定工作,经常需要编写shell脚本程序。shell脚本程序的编写也是系统管理必备的技能之一。
6.通配符Wildcard
bash支持许多命令通配符,可以帮助用户快速的执行命令。例如想要知道/usr/bin目录下有哪些以X开头的文件,就可以向下面这样使用*通配符:
              [root@initroot ~]$ ls -l /usr/bin/X*
            
7.限制用户资源使用ulimit
linux ulimit命令

shell运行模式
shell运行模式
交互式shell 非交互式shell
登录shell 交互式登录shell 非交互式登录shell
非登录shell 交互式非登录shell 非交互式非登录shell

3.登录shell和非登录shell

回到顶部

4.交互式shell和非交互式shell

回到顶部

bash提供了命令历史的服务,可以将做过的操作记录下来!通过history命令可以列出做过的操作。 如果觉得histsory的输入太麻烦,可以设置history的命令别名!

              [peter@study ~]$ alias h='history'
            
输入h等于输入history!!

history命令可以列出以前运行过的命令列表

              [peter@study ~]$ history [n]
              [peter@study ~]$ history [-c]
              [peter@study ~]$ history [-raw] histfiles
            
选项与参数:
n :数字,列出最近的n行历史命令!
-c :将当前shell中的所有history内容全部清除;
-a :将当前新增的history命令追加到histfiles中,若没有加histfiles参数,则默认写入~/.bash_history文件;
-r :将histfiles的内容读到当前shell的history记录中;
-w :将目前的history内容写入histfiles。

列出目前内存中的所有history记录:

              [peter@study ~]$ history
              ...#前面省略
              1017 man bash
              1018 ll
              1019 history
              1020 history
            
#列出的信息当中,共分两栏,第一栏为该命令在这个shell当中的代码,
#另一个则是命令本身!HISTSIZE决定会列出几笔命令记录!
              [peter@study ~]$ echo ${HISTSIZE}
              1000
            
列出目前最近使用的3个命令:
              [peter@study ~]$ history 3
              1019 history
              1020 history
              1021 history 3
            
将目前的记录写入histfile当中:
              [peter@study ~]$ history -w
            
#默认情况下,会将历史纪录写入~/.bash_history文件中!

正常的情况下,历史命令的读取与记录是这样的:
当我们以 bash 登入 Linux 主机之后,系统会主动的由家目录的 ~/.bash_history 读取以前曾经下过的命令, 那么 ~/.bash_history 会记录几笔数据呢?这就与你 bash 的 HISTFILESIZE 这个变量设定值有关了!
假设本次登入主机后,共下达过100次命令,等注销时,系统就会将101~1100总共1000个历史命令更新到~/.bash_history中。
也就是说,历史命令在我注销时,会将最近的HISTFILESIZE笔记录到我的纪录文件当中啦!
基本上history的用途很大的!但是需要小心安全的问题!尤其是root的历史纪录文件,这是Cracker的最爱! 因为不小心的root会将很多的重要数据在执行的过程中会被纪录在~/.bash_history当中,如果这个文件被解析的话,后果不堪!
如果某些重要的操作记录不希望被后来登录的人看到,在退登录前需要使用history -c; history -w来强迫更新纪录文件!
当然,也可以用 history -w 强制立刻写入的!那为何用更新两个字呢? 因为 ~/.bash_history 记录的笔数永远都是 HISTFILESIZE 那么多,旧的讯息会被覆盖掉! 仅保留最新的!

除了使用history查询命令记录以外,我们还可以使用相关的功能来执行命令:

              [peter@study ~]$ !number
              [peter@study ~]$ !command
              [peter@study ~]$ !!
            
选项与参数:
number:执行第几笔命令的意思;
command:由最近的命令向前搜寻命令串开头为command的那个命令,并执行;
!!:就是执行上一个命令(相当于按↑按键+Enter)
              [peter@study ~]$ history
              66 man rm
              67 alias
              68 man history
              69 history
              #执行第66条命令:
              [peter@study ~]$ !66
              #执行上一个命令,本例中亦即!66:
              [peter@study ~]$ !!
              #执行最近以al为开头的命令(上头列出的第67个):
              [peter@study ~]$ !al
            

想要执行上一条命令, 除了使用上下键之外,还可以直接用!!来执行上一条命令。 也可以直接使用!n执行第n条命令, 还可以使用命令标头,例如!vi执行最近命令开头是vi的命令!

同一账号同时多次登入bash,history写入问题
很多linux用户喜欢同时打开多个bash,这些bash的身份都是root。 这样会有~/.bash_history的写入问题。因为多个bash同时以root身份登入, 所有的bash都有自己的1000笔记录在内存中。 等到注销时才会更新记录文件,但是最后注销的那个bash才会是最后写入的数据,最后注销的bash的历史记录会将之前的bash历史记录覆盖掉。 这样其他bash的命令操作就不会被记录下来了。
由于多重登入有这样的问题,所以很多用户都习惯单一bash登入,再用作业控制(job control)来切换不同工作! 这样才能将所有执行过的命令记录下来,方便未来系统管理员进行命令的debug!
无法记录时间
bash无法记录命令执行的时间。历史命令是依序记录的,但是并没有记录时间,所以在查询方面会有一些不方便。 其实可以通过~/.bash_logout来进行history的记录,并加上date来增加时间参数!

bash控制终端环境设置:stty和set

回到顶部

bash前台进程就是系统前台进程,但是bash后台进程不一定是系统后台进程。
在bash环境下有很多的按键功能,例如退格键backspace可以删除命令行上的字符,ctrl+c可以强制终止前台进程。 使用stty命令即可查看当前bash终端各种按键的功能,也可以使用stty重新设置按键功能意义。 stty为setting tty的缩写:

              [peter@study ~]$ stty [-a]
            
-a选项可以列出当前控制终端所有的信息。
列出当前bash控制终端所有的按键信息:
              [peter@study ~]$ stty -a
              speed 38400 baud; rows 35; columns 80; line = 0;
              intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
              eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
              werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
              -parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
              -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel iutf8
              opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
              isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
            
上面输出中的^表示Ctrl键。几个重要的组合键意义如下:
intr : 为interrupt的缩写,给前台进程发送中断信号,强制进程终止退出;
quit : 给前台进程发送quit信号;
erase : 向后删除命令行字符;
kill : 删除命令行上的所有字符;
eof : 为End of file的缩写,表示文件输入的结束;
start : 重新启动被停止stop进程的输出output;
stop : 停止前台进程的屏幕输出;
susp : 发送suspend信号给前台进程,使前台进程进入后台暂停。
erase的功能组合键为crtl+?,现在想把erase的组合键设置为crtl+h:
              [peter@study ~]$ stty erase ^h
            
这样设置后,在命令行删除字符就得使用ctrl+h,按下backspace则会出现^?字样! 恢复利用backspace,就下达stty erase ^?即可! 更多stty的说明可以参考man stty!

在windows环境下,很多软件默认的储存快捷键是crtl+s,如果你经常在Windows和Linux之间切换, 那么在Linux环境下使用vim时,想要保存文件的时候可能会习惯的按下crtl+s!在linux下ctrl+s其实是stop的意思, 这样vim进程就被暂停停止了,无论怎么操作都不会有任何反应,这时我们就可以按下crtl+q让vim进程重新start!
除了利用stty设置控制终端环境外,bash也有自己的一些终端设定值!那就是利用set来设定的! 我们之前提到一些变量时,可以利用set来显示,除此之外,其实set还可以帮我们设定整个命令输出/输入的环境。 例如记录历史命令、显示错误内容等等。

              [peter@study ~]$ set [-uvCHhmBx]
            
选项与参数:
-u :默认不启用。当使用未定义的变量时,会显示错误讯息;
-v :默认不启用。在讯息被输出前,会先显示讯息的原始内容;
-x :默认不启用。在命令被执行前,会显示命令内容,前面有++符号;
-h :默认启用。与历史命令有关;
-H :默认启用。与历史命令有关; -m :默认启用。与作业管理有关;
-B :默认启用。与刮号[]的作用有关;
-C :默认不启用。若使用>等,则若文件存在时,该文件不会被覆盖。
$-变量为当前bash已经设置的所有set设置,使用echo命令显示目前所有的set设定值:
              [peter@study ~]$ echo $-
              himBH
            
可以看到当前bash默认的设置为himBH!
设定bash"若使用未定义变量时,则显示错误讯息":
              [peter@study ~]$ set -u
              [peter@study ~]$ echo $petering
              -bash: petering: unbound variable
            
很多shell都会默认开启该选项,这样如果变量为未定义变量时,bash就会提示错误信息。
输入set +u即可取消该设置!

执行前,显示该命令内容。

              [peter@study ~]$ set -x
              [peter@study ~]$ echo $HOME
              + echo /home/peter
              /home/peter
            
这样设置后,在命令执行之前,bash会先将命令输出到屏幕,前面会有+符号!

其实还有其他的按键设定功能呢!可以在/etc/inputrc文件中设定,还有/etc/DIR_COLORS*与/usr/share/terminfo/*文件等,也都是与终端有关的环境配置文件!
事实上,并不建议修改控制终端tty的环境呢,因为bash的环境已经设定的很亲和了,我们不需要额外的设定或者修改,否则反而会产生一些麻烦。 这里只是向大家介绍控制终端tty是如何进行设置的!

bash默认的组合键汇总如下:

bash默认组合键
bash默认组合键
组合键 组合键功能意义
Ctrl + C intr,为interrupt的缩写,给前台进程发送中断信号,强制当前进程终止退出
Ctrl + D 输入结束EOF,为End of file的缩写,表示文件输入的结束;
Ctrl + M 相当于Enter键!
Ctrl + S stop,暂停屏幕的输出,停止前台进程的屏幕输出;
Ctrl + Q start,重新启动被停止stop进程的输出output,恢复屏幕的输出
Ctrl + U kill,删除命令行上的所有字符;
Ctrl + Z susp,发送suspend信号给前台进程,使前台进程进入后台暂停。暂停目前的命令
Ctrl + ? erase,相当于backspace键,向后删除命令行字符;

通配符与特殊符号

回到顶部

在bash下可以使用通配符(wildcard)匹配各种字符。常用的通配符如下:

bash常用通配符
bash常用通配符
通配符 通配符意义
* 代表0个或任意多个的任意字符
? 代表一个任意字符
[ ] 代表中括号[ ]内出现的任意一个字符。例如[abcd]表示一定有一个字符, 可能是a, b, c, d这四个中的任何一个
[ - ] 括号内[]为减号-,减号两边有字符,表示在字符编码顺序内的任一字符。例如[0-9]代表0到9之间的任一数字,[a-z]代表a到z之间的任一字符
[^ ] 中括号内[ ]的第一个字符为指数符号^, 表示不是中括号内字符的任一字符。例如[^abc]代表一个字符,但是这个字符不可能是a、b或者c。
接下来让我们利用通配符来玩些东西吧!首先,利用通配符配合ls找文件名看看:
列出/etc/目录下以cron开头的文件名:
              #由于与编码有关,先设定语系一下
              [peter@study ~]$ LANG=C
              [peter@study ~]$ ls -ld /etc/cron*
              drwxr-xr-x 2 root root 4096 Jul 16  2019 /etc/cron.d
              drwxr-xr-x 2 root root 4096 Sep 25 12:18 /etc/cron.daily
              drwxr-xr-x 2 root root 4096 Dec 17  2018 /etc/cron.hourly
              drwxr-xr-x 2 root root 4096 Dec 17  2018 /etc/cron.monthly
              -rw-r--r-- 2 root root  722 Nov 16  2017 /etc/crontab
              drwxr-xr-x 2 root root 4096 Dec 17  2018 /etc/cron.weekly
            
-d选项如果是目录仅列出目录本身的信息,不需要列出目录内的文件信息。
列出/etc/目录下文件名刚好是五个字母的文件名:
              [peter@study ~]$ ls -ld /etc/??????
              drwxr-xr-x 4 root root   4096 Jul 10  2019 /etc/apport
              drwxr-xr-x 7 root root   4096 Dec 17  2018 /etc/brltty
              drwxr-xr-x 2 root root   4096 Jul 16  2019 /etc/cron.d
              ...省略...
            
每一个?代表一个任意字符,五个字符就是五个?了。
列出/etc/目录下文件名含有数字的文件名:
              [peter@study ~]$ ls -ld /etc/*[0-9]*
              drwxr-xr-x  4 root root  4096 Dec 17  2018 /etc/dbus-1
              drwxr-xr-x  3 root root  4096 Jul 16  2019 /etc/gnome-vfs-2.0
              drwxr-xr-x  2 root root  4096 Dec 17  2018 /etc/gtk-2.0
              ...省略...
            
列出/etc/目录下, 文件名开头不是a-t的所有文件名:
              [peter@study ~]$ ls -ld /etc/[^a-t]*
            
将上面找到的文件复制到/tmp/upper目录中
              [peter@study ~]$ mkdir /tmp/upper
              [peter@study ~]$ cp -a /etc/[^a-z]* /tmp/upper
            
除了使用通配符, bash环境下还有很多的特殊字符,这些特殊字符具有特定的功能含义。 理论上,文件名尽量不要使用这些特殊字符!常用的特殊字符汇总如下:

bash常用特殊字符
bash常用特殊字符
特殊字符 特殊字符意义
# 注释符号,#后面的任何字符都被bash作为注释说明,没有实际的执行意义,最常用在shell script中
\ 转义字符,将特殊字符或通配符还原成一般字符
| 管道线(pipe),连接两个管道命令。管道线前面命令的输出作为管道线后面命令的输入
; 连续命令分隔符,连接两个按先后顺序连续执行的命令。分割符前后的两个命令可以没有任何关系。但是一定是按照顺序执行。
$ 变量前导符,取变量的值
& 作业控制命令(job control),将命令变成bash后台作业
! 逻辑运算意义上的非 not 的意思!
/ 路径分隔符,相对路径或绝对路径父子目录或文件的分割符
>, >> 数据流输出重定向符号,>为覆盖输出,>>为追加输出
<, << 数据流输入重定向符号,分别为覆盖输入和追加输入
' ' 单引号,单引号内的字符均为普通字符,即使是特殊字符也失去了特殊功能,例如$失去取变量的功能含义,变为普通字符
" " 双引号,双引号内的特殊字符具有特殊功能,例如$仍然保留取变量值的功能。
` ` 反单引号`内为可优先执行的命令,意义类似$( )
( ) 在()中间为子shell 的起始与结束
{ } 在{}中间为命令区块的组合!

bash Shell的变量

回到顶部

变量是bash非常重要的一个概念,我们将介绍重要的环境变量、变量的查看和设置等

什么是变量?

回到顶部

自定义普通变量只在当前bash环境下有效,并不会影响到bash创建的子进程。 而环境变量可以影响到bash的子进程。
某些特定变量会影响到bash的环境!比如我们之前多次提到过的$PATH环境变量! 我们在命令行下执行一条命令的时候,bash就是从$PATH环境变量所定义的目录顺序寻找命令的可执行文件。 如果有多个匹配的可执行文件,bash只会执行第一个找个的可执行文件。 如果找不到匹配的可执行文件,bash就会提示command not found。
常用的环境变量有PATH、HOME、MAIL、SHELL等,为了区别与自定义变量的不同,环境变量通常以大写字符来表示!

变量的定义与显示

回到顶部

可以使用echo命令显示变量的值,引用变量的时候需要在变量名前面加上$符号,或者以${变量名}的形式。$符表示取出变量值的意思。 目前越多来多的是采用${变量名}的形式。echo命令显示变量名的常用格式如下所示:

              [peter@study ~]$ echo $variable 或 ${variable}
            
例如,显示PATH环境变量的值:
              [peter@study ~]$ echo $PATH
              /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
              [peter@study ~]$ echo ${PATH}
              /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
            
echo的功能还有很多, 这里只是显示变量值,更多echo的信息,请自行查看manpage:man echo。

我们知道如何查看一个变量的值了,但是如何定义一个变量呢?直接用等号(=)连接变量名与变量值即可! 例如定义一个变量myname,变量值为peter,我们先通过echo查看一下这个变量是否已经被定义过了:

              [peter@study ~]$ echo ${myname}

            
上面输出一个空行,说明这个变量还没有被定义!下面给变量myname设置变量值为peter:
              [peter@study ~]$ myname=peter
            
再次打印变量的值:
              [peter@study ~]$ echo ${myname}
              peter
            
我们成功定义了一个变量myname,变量值为peter。在bash中,当一个变量名称尚未被设置时,默认的变量值就是空的。 这里要注意每个shell的语法都是不相同的,echo是bash的内置命令,在bash下echo一个没有定义的变量,显示的就是空行, 如果在其他shell环境下echo一个没有定义的变量,可能会出现错误信息!

echo命令可以用来回显字符串到标准输出,常用来显示变量的值,显示变量的时候,需要在变量前面加上$号。
回显字符串'Initroot is a website for learning Linux':

              [peter@study ~]$ echo Initroot is a website for learning Linux
                Initroot is a website for learning Linux
              [peter@study ~]$ echo 'Initroot is a website for learning Linux'
              Initroot is a website for learning Linux
            
显示环境变量HOME:
              [peter@study ~]$ echo $HOME
              /home/peter
            

echo命令用于在shell中打印shell变量的值,或者直接输出指定的字符串。 linux的echo命令,在shell编程中极为常用, 在终端下打印变量value的时候也是常常用到的,因此有必要了解下echo的用法。 echo命令的功能是在显示器上显示一段文字,一般起到一个提示的作用。

变量命名规则

回到顶部

bash变量的设置需要遵循一定的变量命名规则,变量的设置规则如下所示:
1. 变量名与变量值用等号=连接,例如myname=peter;
2. 等号=两边以及变量名不能有空格符,例如myname = peter或myname=peter Tsai都是错误的变量设置方式;
3. 变量名由英文字母或数字组成,不能以数字开头,例如2myname=peter就是错误的变量名设置方式;
4. 变量值如果有空格符, 需要使用双引号"或单引号'将变量值括起来。双引号内的特殊字符如$, 还是保留原本的功能特性。而单引号内的特殊字符则为一般文本字符。
假如LANG的变量值为zh_TW.UTF-8,我们定义一个var变量:var="lang is $LANG", 那么通过echo显示该变量的值为lang is zh_TW.UTF-8, 如果将var变量值用单引号:var='lang is $LANG', 那么通过echo显示该变量的值就是lang is $LANG。
单引号与双引号的最大不同在于双引号仍然可以保持特殊符号的特殊功能,但单引号内的特殊字符会作为一般普通字符处理,并不会有特别的作用。
5. 可用转义字符\将特殊符号(如[Enter], $, \, 空格符, '等)变成一般字符,如:myname=peter\ Tsai;
6. 变量值可以通过其他命令的输出获得,带命令的变量值形式为:`命令`或 $(命令)。其中`为反单引号,在键盘上为数字键1左边那个按键。
例如我们通过uname -r命令可以获得linux内核的版本号:

              [peter@study ~]$ uname -r
              4.15.0-65-generic
            
将uname -r的输出作为变量值赋值给一个变量kernelversion:
              [peter@study ~]$ kernelversion=$(uname -r)
            
显示变量kernelversion的值:
              [peter@study ~]$ echo $kernelversion
              4.15.0-65-generic
            
7. 可以对变量值进行累加,例如原变量为myname=peter,现在要将myname变量设置为peterpeng,只需要在原变量值的基础上追加peng即可。 原变量值可用"$变量名"或${变量名}两种形式。追加方式如下: myname="$myname"peng 或者 myname=${myname}peng。命令行示例如下:
              [peter@study ~]$ echo myname
              myname
              [peter@study ~]$ myname=peter
              [peter@study ~]$ echo $myname
              peter
              [peter@study ~]$ myname="$myname"peng
              [peter@study ~]$ echo $myname
              peterpeng
              [peter@study ~]$ myname=${myname}peng
              [peter@study ~]$ echo $myname
              peterpengpeng
            
上面在执行myname=${myname}peng之前,由于myname已经是peterpeng了,所有再使用${myname}peng形式追加的时候,变量值就变成peterpengpeng。
我们也可以给变量直接赋值一个新的变量值,例如上面直接给myname设置新值peterpeng:myname=peterpeng。
既然可以直接设置新值,为什么还要这么麻烦的追加呢?如果遇到变量值比较长,重新输入就会比较麻烦,所以更方便的做法就是在原来变量值的基础上追加就可以了, 比较常用的是给PATH环境变量追加新的目录。例如:PATH="$PATH":/home/bin或PATH=${PATH}:/home/bin
8. 用export将自定义普通变量变为环境变量:export PATH,环境变量可以影响bash的子进程,我们稍后讨论这种影响。
9. 通常大写字符为系统默认变量,自定义普通变量建议使用小写字符,方便判断;
10. 使用unset命令取消变量的定义:unset 变量名。
取消上面myname的定义:
              [peter@study ~]$ unset myname
              peter@peter-VirtualBox:~$ echo $myname

            

定义变量name,变量值为peter:

              [peter@study ~]$ 1myname=peter
              1myname=peter: command not found #屏幕提示错误!因为不能以数字开头!
              [peter@study ~]$ myname = peter
              myname: command not found #因为有空白!提示错误
              [peter@study ~]$ name=peter
            
如果变量值为peter's name,变量值含有特殊字符,该如何输入呢?:
              [peter@study ~]$ name=peter's name
              > 
            
按下enter键后,bash给出了续行符,这是由于单引号与双引号必须成对出现,上面已经输入了一个',当你按下enter后, bash认为你还没有输入完,所以就会给出续行符等待用户继续输入。直到你输入另一个成对的单引号或者双引号,然后再按下enter后,才算结束。 当然这不是我们需要的。只需要按下ctrl-c结束即可!
              [peter@study ~]$ name="peter's name"
            
用双引号括起来后,里面的单引号'就变成一般文本字符了,这样就可以成功定义了.
              [peter@study ~]$ name='peter's name'
              >
            
如果使用单引号',那么也会出现上面的问题,因为前两个单引号已成对,多了最后一个单引号'没有成对,bash还是认为用户没有输入完。
              [peter@study ~]$ name=peter\'s\ name
            
上面使用转义字符反斜杠\转义特殊字符,例如单引号与空格键,这样也可以成功定义变量!
给PATH变量累加/home/peter/bin目录:
              [peter@study ~]$ PATH=$PATH:/home/peter/bin[peter@study ~]$ PATH="$PATH":/home/peter/bin[peter@study ~]$ PATH=${PATH}:/home/peter/bin
            
上面三种形式都可以成功累加PATH环境变量。 当然并不推荐第一种形式,因为可能会出现问题,例如将myname变量值追加peng:
              [peter@study ~]$ myname=$mynamepeng
            
上面相当于是将mynamepeng变量的变量值赋值给myname变量了。但是根本就不存在mynamepeng这个变量,所以就会出现问题了。 正确的做法是:
              [peter@study ~]$ myname="$myname"peng[peter@study ~]$ myname=${myname}peng
            

我们刚才提到可以将自定义普通变量转变为环境变量,不管是普通变量还是环境变量,本质上都是bash的变量。但是这两种变量又有一些区别, 主要的区别是自定义普通变量不可以被bash的子进程继承,而环境变量可以被bash的子进程继承。 换句话说,自定义普通变量只存在当前bash中,在bash的子进程中就不存在了。而环境变量在bash的子进程中依然有效。我们通过实例观察两者的区别:

              [peter@study ~]$ myname=peter
              [peter@study ~]$ echo $myname
              peter
              [peter@study ~]$ bash
              [peter@study ~]$ echo $myname

            
上面我们定义了myname变量,通过echo打印变量的值为peter。然后进入另一个bash,此时相当于进入了第一个bash的子进程中了,再次用echo打印myname变量值,发现已经为空了。 说明自定义变量在bash的子进程中已经失效了。我们用exit命令退出bash子进程,回到刚才的bash,然后通过export命令将myname变为环境变量,重复执行刚才的操作,再次进入bash的子进程:
              [peter@study ~]$ exit
              [peter@study ~]$ export myname
              [peter@study ~]$ echo $myname
              peter
              [peter@study ~]$ bash
              [peter@study ~]$ echo $myname
              peter
            
将myname变量变为环境变量后,在bash的子进程中就可以显示该变量的值了,说明在bash的子进程中该变量值仍然是有效的。相当于bash的子进程继承了父进程的环境变量。

退出当前bash,回到刚才的bash环境。

              [peter@study ~]$ exit
            
进入当前系统内核模块目录:
              [peter@study ~]$ cd /lib/modules/`uname -r`/kernel
              或者
              [peter@study ~]$ cd /lib/modules/$(uname -r)/kernel
            
推荐使用$(uname -r)的形式。每个linux发行版所用的linux内核版本可能都是不一样的, 所以也就会对应不同的内核模块目录。例如CentOS 7.1默认内核版本是3.10.0-229.el7.x86_64, 内核模块目录为/lib/modules/3.10.0-229.el7.x86_64/kernel/。
在编写shell脚本的时候,可能需要进入该目录。 如果在shell脚本中写cd /lib/modules/3.10.0-229.el7.x86_64/kernel/, 那么把这个脚本放到另外的linux发行版中可能就无法成功运行。因为内核版本号发生了变化,相应的内核模块目录也就发生变化了。
这时候就可以利用uname -r命令先取得内核版本信息。将该命令以变量的形式加入命令中。 这样就可以不需要更改shell脚本源代码在各个linux发行版中成功运行了。
在一串命令中,在反单引号`之内的命令将会被先执行,而其执行出来的结果将做为外部的输入信息! 例如 uname -r会显示出目前的内核版本,而我们的内核版本在 /lib/modules 里面。 因此,可以先执行uname -r找出内核版本,然后再以cd目录到该目录下。

locate命令可以列出所有的相关文件名,但是,如果我想要知道各个文件的权限呢?例如想知道每个crontab相关文件的权限:

              [peter@study ~]$ ls -ld `locate crontab`
              [peter@study ~]$ ls -ld $(locate crontab)
            
先以locate将文件名数据都列出来,再以ls命令来处理!

取消变量myname的定义:

              [peter@study ~]$ echo $myname
              peter
              [peter@study ~]$ unset myname
              [peter@study ~]$ echo $myname
            
以上就是变量的设置和取消的内容了,比较重要的是一些特殊符号的使用,例如单引号'、双引号"、转义字符/、美元字符$、反单引号`等!

如果有一个常去的工作目录:/home/wwwroot/default/kblog/static/,如何进行该目录的简化?
在一般的情况下,如果你想要进入上述的目录得要cd /home/wwwroot/default/kblog/static/, 但如此一来变换目录就很麻烦。 此时就可以将目录定义为变量,如下所示:

              [peter@study ~]$ work="/home/wwwroot/default/kblog/static/"
              [peter@study ~]$ cd $work
            
上面我们在bash中定义的变量,即使没有用unset命令取消,在bash退出后也就自动消失了。这样在下次启动bash后,这些变量也就没有了。 那么如何才能保留这些变量的设置呢?只需要将这些变量的定义写入bash的配置文件~/.bashrc中即可。

declare和typeset

回到顶部

declare或typeset是一样的功能,就是在声明变量的类型。 如果declare后面并没有接任何参数,那么bash就会将所有的变量名称与内容通通列出来,就好像使用set一样! declare常用语法格式如下所示:

              [peter@study ~]$ declare [-aixr] variable
            
选项与参数:
-a :将variable变量定义为数组array类型;
-i :将variable变量定义为整型数值integer;
-x :用法与export命令一样,将variable变量变成环境变量;
-r :将变量设定成为只读类型readonly,该变量不可被更改内容,也不能unset;
让变量sum进行100+300+50的加总结果:
              [peter@study ~]$ sum=100+300+50
              [peter@study ~]$ echo ${sum}
              100+300+50
            
怎么没有帮我计算总和?变量的默认类型为字符串类型.所以上面并没有做算数运算!
              [peter@study ~]$ declare -i sum=100+300+50
              [peter@study ~]$ echo ${sum}
              450
            
在默认情况, bash变量类型为字符串.
bash中的数值运算,默认最多仅能到达整数形态,所以1/3结果是0;
将sum变成环境变量
              [peter@study ~]$ declare -x sum
              [peter@study ~]$ export | grep sum
              declare -ix sum="450"
            
可以看到sum的定义有i和x!
让sum变成只读属性:
              [peter@study ~]$ declare -r sum
              [peter@study ~]$ sum=tesgting
              -bash: sum: readonly variable
            
这样就无法改变sum的变量值了.如果你不小心将变量设定为只读,通常得要注销再登入才能复原该变量的类型了!
让sum恢复成非环境变量的自定义变量:
              [peter@study ~]$ declare +x sum
              [peter@study ~]$ declare -p sum
              declare -ir sum="450"
            
上面我们使用+x取消环境变量属性,然后用-p观察变量的定义类型,只剩下ir的类型,不具有x了!

数组 (array) 变量类型

回到顶部

某些时候,我们必须使用数组来声明一些变量。bash数组的设定方式是:
var[index]=content
意思是说,数组名为var,而这个数组的内容为var[1]=小明, var[2]=大明, var[3]=好明 ....等等,index就是一些数字,重点是用中刮号[ ])来设定的。 bash提供的是一维数组。数组通常与循环或者其他判断式交互使用才有比较高的存在意义!
设置上面提到的var[1] ~ var[3]的变量:

              [peter@study ~]$ var[1]="small min"
              [peter@study ~]$ var[2]="big min"
              [peter@study ~]$ var[3]="nice min"
              [peter@study ~]$ echo "${var[1]}, ${var[2]}, ${var[3]}"
              small min, big min, nice min
            
数组的变量类型比较有趣的地方在于读取,一般来说,建议直接以${数组}的方式来读取!这也是为啥一开始就建议你使用${变量}来记忆的原因!

普通变量和环境变量

回到顶部

自定义变量的范围只作用于当前bash进程环境,而环境变量的有效范围为当前bash及其子进程。 自定义变量类似局部变量local variable,而环境变量类似全局变量global variable.
理论上是这样的:
当启动一个shell,操作系统会为shell进程分配一内存区存放环境变量,若在父程序利用export功能,可以让自定义变量的内容写到上述的内存区中(环境变量);
当加载另一个shell时,即启动子进程,而离开原本的父进程,子shell进程可以将父shell进程的环境变量所在的内存区导入自己的环境变量内存区中。
不过要提醒的是,环境变量与bash的操作环境不是一回事儿,举例来说, PS1并不是环境变量,但是这个PS1会影响到bash的接口环境提示字符

变量键盘读取: read

回到顶部

定义变量的时候,直接在命令行输入"变量=变量值".
我们经常会遇到在程序的执行过程中会要求用户输入相关的信息,例如最常见的是等待用户输入yes/no,这类程序称为交互式程序.
shell script在运行的过程中,也经常需要和用户交互,等待用户输入,将用户输入的信息存放在变量中.这时候就需要用到read命令了.
在命令行用"变量=变量值"定义变量的时候,变量默认的类型是字符串类型.如果我们想指定变量的类型,例如数组或者数值,就需要用到declare命令.
read读取来自键盘的输入,并将输入存入变量。read命令常用格式如下:

              [peter@study ~]$ read [-pt] variable
            
选项与参数:
-p :后面可以接提示字符!
-t :后面接等待的秒数!
将用户输入作为readtest变量的变量值:
              [peter@study ~]$ read readtest
              this is a read test #输入enter后,光标会等待输入,输入本行内容后按enter
              [peter@study ~]$ echo ${readtest}
              this is a read test
            
提示用户30秒内输入自己的大名,将该输入字符串作为named变量内容:
              [peter@study ~]$ read -p "Please keyin your name: " -t 30 named
              Please keyin your name: peter peng #注意看,会有提示字符!
              [peter@study ~]$ echo ${named}
              peter peng
            
read之后不加任何参数,直接加上变量名,按下enter后就会主动出现一个空白行等待用户的输入。 如果加上-t后面接秒数,指定的秒数之内没有任何动作时, 该命令就会自动略过. 如果加上-p,在输入的光标前就会有比较多可以用的提示字符给我们参考!多用于交互式shell。

变量内容的删除、取代与替换 (Optional)

回到顶部

变量除了可以直接设定内容之外,还可以进行变量内容的删除、取代与替换!
我们使用PATH这个变量的内容来做测试好了。
设置小写的path自定义变量与环境变量PATH相同:

              [peter@study ~]$ path=${PATH}
              [peter@study ~]$ echo ${path}
              /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/peter/.local/bin:/home/peter/bin
            
假设我不喜欢local/bin,所以要将前1个目录删除掉,如何显示?
              [peter@study ~]$ echo ${path#/*local/bin:}
              /usr/bin:/usr/local/sbin:/usr/sbin:/home/peter/.local/bin:/home/peter/bin
              上面这个范例很有趣的!他的重点可以用底下这张表格来说明:
              ${variable#/*local/bin:}
              上面的特殊字体部分是关键词!用在这种删除模式所必须存在的
              ${variable#/*local/bin:}
              这就是原本的变量名称,以上面范例二来说,这里就填写 path 这个变量名称啦!
              ${variable#/*local/bin:}
              这是重点!代表从变量内容的最前面开始向右删除,且仅删除最短的那个
              ${variable#/*local/bin:}
            
代表要被删除的部分,由于 # 代表由前面开始删除,所以这里便由开始的 / 写起。 需要注意的是,我们还可以透过通配符 * 来取代 0 到无穷多个任意字符以上面范例二的结果来看, path这个变量被删除的内容如下所示:
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/peter/.local/bin:/home/peter/bin
很有趣吧!这样了解了 # 的功能了吗?
我想要删除前面所有的目录,仅保留最后一个目录
              [peter@study ~]$ echo ${path#/*:}
              /usr/bin:/usr/local/sbin:/usr/sbin:/home/peter/.local/bin:/home/peter/bin
              # 由于一个 # 仅删除掉最短的那个,因此他删除的情况可以用底下的删除线来看:
              # /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/peter/.local/bin:/home/peter/bin
            
              [peter@study ~]$ echo ${path##/*:}
              /home/peter/bin
            
# 嘿!多加了一个 # 变成 ## 之后,他变成删除掉最长的那个数据!亦即是:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/peter/.local/bin:/home/peter/bin非常有趣! 不是吗?因为在 PATH 这个变量的内容中,每个目录都是以冒号:隔开的, 所以要 从头删除掉目录就是介于斜线 (/) 到冒号 (:) 之间的数据!但是 PATH 中不止一个冒号 (:) 啊! 所 以 # 与 ## 就分别代表:
# :符合取代文字的最短的那一个;
##:符合取代文字的最长的那一个
上面谈到的是从前面开始删除变量内容,那么如果想要从后面向前删除变量内容呢? 这个 时候就得使用百分比 (%) 符号了!来看看范例四怎么做吧!
范例四:我想要删除最后面那个目录,亦即从 : 到 bin 为止的字符串
              [peter@study ~]$ echo ${path%:*bin}
              /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/peter/.local/bin
            
# 注意啊!最后面一个目录不见去!
# 这个 % 符号代表由最后面开始向前删除!所以上面得到的结果其实是来自如下:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/peter/.local/bin:/home/peter/bin
那如果我只想要保留第一个目录呢?
              [peter@study ~]$ echo ${path%%:*bin}
              /usr/local/bin
            
# 同样的, %% 代表的则是最长的符合字符串,所以结果其实是来自如下:
# /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/peter/.local/bin:/home/peter/bin
由于我是想要由变量内容的后面向前面删除,而我这个变量内容最后面的结尾是/home/peter/bin,
所以你可以看到上面我删除的数据最终一定是bin,亦即是:*bin那个 * 代表通配符! 至于 %
与 %% 的意义其实与 # 及 ## 类似!这样理解否?
例题:
假设你是 peter ,那你的 MAIL 变量应该是 /var/spool/mail/peter 。假设你只想要保留最后面那个文件名 (peter), 前面的目录名称都不要了,如何利用 $MAIL 变量来达成?
答:
题意其实是这样/var/spool/mail/peter,亦即删除掉两条斜线间的所有数据(最长符合)。 这个时候你就可 以这样做即可:
              [peter@study ~]$ echo ${MAIL##/*/}
            
相反的,如果你只想要拿掉文件名,保留目录的名称,亦即是/var/spool/mail/peter (最短符合)。但假设 你并不知道结尾的字母为何,此时你可以利用通配符来处理即可,如下所示:
              [peter@study ~]$ echo ${MAIL%/*}
            
了解了删除功能后,接下来谈谈取代吧!
将path的变量内容内的sbin取代成大写SBIN:
              [peter@study ~]$ echo ${path/sbin/SBIN}
                /usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/sbin:/home/peter/.local/bin:/home/peter/bin
            
# 这个部分就容易理解的多了!关键词在于那两个斜线,两斜线中间的是旧字符串 # 后面的是新字符串,所以结果就会出现如上述的特殊字体部分啰!
              [peter@study ~]$ echo ${path//sbin/SBIN}
              /usr/local/bin:/usr/bin:/usr/local/SBIN:/usr/SBIN:/home/peter/.local/bin:/home/peter/bin
            
# 如果是两条斜线,那么就变成所有符合的内容都会被取代喔!
我们将这部份作个总结说明一下:

变量设定方式

回到顶部

说明
${变量#关键词}
${变量##关键词} 若变量内容从头开始的数据符合关键词,则将符合的最短数据删除
若变量内容从头开始的数据符合关键词,则将符合的最长数据删除
${变量%关键词}
${变量%%关键词} 若变量内容从尾向前的数据符合关键词,则将符合的最短数据删除
若变量内容从尾向前的数据符合关键词,则将符合的最长数据删除
${变量/旧字符串/新字符串}
${变量//旧字符串/新字符串} 若变量内容符合旧字符串则第一个旧字符串会被新字符串取代
若变量内容符合旧字符串则全部的旧字符串会被新字符串取代
变量的测试与内容替换
在某些时刻我们常常需要判断某个变量是否存在,若变量存在则使用既有的设定,若变量不存在 则给予一个常用的设定。 我们举底下的例子来说明好了,看看能不能较容易被你所理解呢!
范例一:测试一下是否存在 username 这个变量,若不存在则给予 username 内容为 root

              [peter@study ~]$ echo ${username}
              #由于出现空白,所以 username 可能不存在,也可能是空字符串
              [peter@study ~]$ username=${username-root}
              [peter@study ~]$ echo ${username}
              root
              #因为 username 没有设定,所以主动给予名为 root 的内容。
              [peter@study ~]$ username="peter tsai"#主动设定 username 的内容
              [peter@study ~]$ username=${username-root}
              [peter@study ~]$ echo ${username}
            
peter tsai #因为 username 已经设定了,所以使用旧有的设定而不以 root 取代 在上面的范例中,重点在于减号 - 后面接的关键词!基本上你可以这样理解: new_var=${old_var-content}
新的变量,主要用来取代旧变量。新旧变量名称其实常常是一样的 new_var=${old_var-content}这是本范例中的关键词部分!必须要存在的哩!
new_var=${old_var-content}
旧的变量,被测试的项目!
new_var=${old_var-content}
变量的内容,在本范例中,这个部分是在给予未设定变量的内容
不过这还是有点问题!因为 username 可能已经被设定为空字符串了!果真如此的话,那你还可
以使用底下的范例来给予 username 的内容成为 root 喔!
范例二:若 username 未设定或为空字符串,则将 username 内容设定为 root
              [peter@study ~]$ username=""
              [peter@study ~]$ username=${username-root}
              [peter@study ~]$ echo ${username}
              #因为 username 被设定为空字符串了!所以当然还是保留为空字符串!
              [peter@study ~]$ username=${username:-root}
              [peter@study ~]$ echo ${username}
              root
            
#加上 : 后若变量内容为空或者是未设定,都能够以后面的内容替换!
在大括号内有没有冒号 : 的差别是很大的!加上冒号后,被测试的变量未被设定或者是已被设 定为空字符串时, 都能够用后面的内容 (本例中是使用 root 为内容) 来替换与设定!这样可以了解 了吗?除了这样的测试之外, 还有其他的测试方法喔!将他整理如下:
底下的例子当中,那个 var 与 str 为变量,我们想要针对 str 是否有设定来决定 var Tips
的值喔! 一般来说, str: 代表str 没设定或为空的字符串时;至于 str 则仅为没有该变数

变量设定方式
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=$strvar=${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
根据上面这张表,我们来进行几个范例的练习吧! ^_^!首先让我们来测试一下,如果旧变量 (str) 不 存在时, 我们要给予新变量一个内容,若旧变量存在则新变量内容以旧变量来替换,结果如下:
测试:先假设 str 不存在 (用 unset) ,然后测试一下减号 (-) 的用法:
              [peter@study ~]$ unset str; var=${str-newvar}
              [peter@study ~]$ echo "var=${var}, str=${str}"
              var=newvar, str=
            
#因为 str 不存在,所以 var 为 newvar 测试:若 str 已存在,测试一下 var 会变怎样?:
              [peter@study ~]$ str="oldvar"; var=${str-newvar}
              [peter@study ~]$ echo "var=${var}, str=${str}"
              var=oldvar, str=oldvar
            
#因为 str 存在,所以 var 等于 str 的内容 关于减号 (-) 其实上面我们谈过了!这里的测试只是要让你更加了解,这个减号的测试并不会影响到 旧变量的内容。 如果你想要将旧变量内容也一起替换掉的话,那么就使用等号 (=) 吧!
测试:先假设 str 不存在 (用 unset) ,然后测试一下等号 (=) 的用法:
              [peter@study ~]$ unset str; var=${str=newvar}
              [peter@study ~]$ echo "var=${var}, str=${str}"
              var=newvar, str=newvar
            
#因为 str 不存在,所以 var/str 均为 newvar 测试:如果 str 已存在了,测试一下 var 会变怎样?
              [peter@study ~]$ str="oldvar"; var=${str=newvar}
              [peter@study ~]$ echo "var=${var}, str=${str}"
              var=oldvar, str=oldvar
            
#因为 str 存在,所以 var 等于 str 的内容 那如果我只是想知道,如果旧变量不存在时,整个测试就告知我有错误,此时就能够使用问号 ?
的帮忙啦! 底下这个测试练习一下先!
测试:若 str 不存在时,则 var 的测试结果直接显示 "无此变量"
              [peter@study ~]$ unset str; var=${str?无此变数}
              -bash: str: 无此变量
            
#因为 str 不存在,所以输出错误讯息
测试:若 str 存在时,则 var 的内容会与 str 相同!
              [peter@study ~]$ str="oldvar"; var=${str?novar}
              [peter@study ~]$ echo "var=${var}, str=${str}"
                var=oldvar, str=oldvar
            
#因为 str 存在,所以 var 等于 str 的内容 基本上这种变数的测试也能够透过 shell script 内的 if...then... 来处理, 不过既然 bash 有提供这么 简单的方法来测试变量,那我们也可以多学一些嘛! 不过这种变量测试通常是在程序设计当中比较 容易出现,如果这里看不懂就先略过,未来有用到判断变量值时,再回来看看吧! ^_^

环境变量的功能

回到顶部

环境变量也是bash的变量,但是和bash的普通变量又有所区别,环境变量可以被bash的子进程继承,而普通变量不可以被继承。 环境变量可以帮我们达到很多功能,包括家目录的切换、命令行提示符的定制显示、命令可执行文件搜索的路径等。
可以通过env和export命令查看当前bash下的所有环境变量。
用env观察环境变量与常见环境变量说明
列出当前shell下所有的环境变量:

              [peter@study ~]$ env
              HOSTNAME=study.centos.peter #主机名
              TERM=xterm #终端机类型
              SHELL=/bin/bash #当前Shell
              HISTSIZE=1000 #历史命令的记录条数,CentOS默认1000条
              OLDPWD=/home/peter #上一个工作目录
              LC_ALL=en_US.utf8 #语系相关
              USER=peter #用户名
              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... #颜色显示
              MAIL=/var/spool/mail/peter #用户mailbox位置
              PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/peter/.local/bin:/home/peter/bin#命令执行搜索路径
              PWD=/home/peter #当前用户工作目录(利用pwd取出!)
              LANG=zh_TW.UTF-8 #语系相关
              HOME=/home/peter #用户家目录
              LOGNAME=peter # 登入者用来登入的账号名称
              _=/usr/bin/env # 上一次使用的命令的最后一个参数(或命令本身)
            
env是environment(环境)的简写,列出所有的环境变量。使用export也会是一样的内容,export还有其他额外的功能,我们稍后介绍。 下面是一些常用环境变量的介绍:
HOME:代表用户的家目录。我们使用cd ~或者直接cd切换到用户家目录。读取的就是这个变量, 有很多程序都可能会读取到这个变量;
SHELL:当前环境运行的SHELL程序,Linux默认使用/bin/bash;
HISTSIZE:历史命令相关,bansh可以记录的命令的最大条数;
MAIL:使用mail命令收信时,系统会去读取的邮件信箱文件(mailbox);
PATH:执行文件的搜寻路径,目录与目录中间以冒号(:)分隔, 文件的搜寻是依序由PATH的变量内的目录来查询;
LANG:语系相关。一般来说,中文编码通常是zh.Big5或者是zh.UTF-8;
RANDOM:随机数变量!目前大多数的distributions都会有随机数生成器,那就是/dev/random这个文件。 我们可以透过这个随机数文件相关的变量($RANDOM)来随机取得随机数。 在BASH的环境下,RANDOM变量介于0~32767之间,echo $RANDOM时,系统就会主动的随机取出一个介于0~32767的数值。 会随机取出0~9之间的数值,利用declare声明数值类型:
              [peter@study ~]$ declare -i number=$RANDOM*10/32768 ; echo $number
              8
            

用set观察所有变量 (含环境变量与自定义变量)
bash可不只有环境变量,还有一些与bash操作接口相关的变量,以及用户自己定义的变量。 使用set命令可以观察这些变量,除了环境变量之外,set还会将bash内的所有变量都显示出来!
env和export只能列出当前bash的环境变量,但是如果想要查看当前bash的所有变量,就得用到set命令,set命令除了列出当前bash的环境变量,也会列出bash中的自定义变量。

              [peter@study ~]$ set
              BASH=/bin/bash #bash的主程序路径
              BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu")
              BASH_VERSION='4.2.46(1)-release' #这两行是bash的版本!
              COLUMNS=90 #当前终端机环境下,字段的字符长度
              HISTFILE=/home/peter/.bash_history #保存历史命令的文件
              HISTFILESIZE=1000 #(与上个变量有关)历史记录文件保存的命令最大条数。
              HISTSIZE=1000 #目前环境下,内存中记录的历史命令最大条数。
              IFS=$' \t\n' #默认的分隔符
              LINES=20 #当前终端机的最大行数
              MACHTYPE=x86_64-redhat-linux-gnu #机器类型
              OSTYPE=linux-gnu #操作系统的类型!
              PS1='[\u@\h \W]\$ ' 命令提示字符,决定了命令提示符的显示。也就是我们常见的[root@www ~]#或[peter ~]$的设定值啦!
              PS2='> ' #使用换行符(\)之后的提示字符
              $ #当前shell进程的PID
              ? #上一条命令执行的返回值。
              ...
            
一般环境变量都会用大写字母,以表示和普通变量的区别。 不过并不是所有大写形式的变量都是环境变量,有一些bash自己定义的变量也会用大写字母表示,这些变量和bash的设置有关,例如PS变量。
上面比较重要的变量有下面这几个:
$:当前shell进程的PID(Process ID).想要知道当前shell进程的PID,可以用:echo $$!出现的数字就是PID号。
?:上一条命令执行的返回值。问号也是一个特殊的变量,命令在执行完后都会返回一个执行后的代码。 一般来说,命令成功执行会返回0,如果执行过程发生错误,就会返回非0的错误代码。
我们以底下的例子来看看:
              [peter@study ~]$ echo $SHELL
              /bin/bash #可顺利显示!没有错误!
              [peter@study ~]$ echo $?
              0 #没问题,返回值为0
              [peter@study ~]$ 12name=peter
              bash: 12name=peter: command not found... #发生错误了!bash 回报有问题
              [peter@study ~]$ echo $?
              127 #有问题,返回错误代码(非0),错误代码返回值依据软件而有不同,可以利用这个返回值来查找错误的原因!
              [peter@study ~]$ echo $?
              0 #怎么又变成正确了?"?"只与上一个执行命令有关,上一个命令echo $?正确执行没有错误,所以是0!
            
OSTYPE, HOSTTYPE, MACHTYPE:主机硬件与内核的等级。目前个人计算机的CPU主要分为32/64位,其中32位又可分为i386,i586,i686,而64位则称为x86_64。 由于不同等级的CPU命令集不太相同,因此软件可能会针对某些CPU进行优化,以求取较佳的软件性能。
所以软件就有i386, i686及x86_64之分。目前主流硬件几乎都是x86_64! 因此CentOS 7开始,已经不支持 i386兼容模式的安装光盘了! 要留意的是,较高阶的硬件通常会向下兼容旧有的软件,但较高阶的软件可能无法在旧机器上面安装! 你可以在x86_64的硬件上安装i386的Linux操作系统,但是无法在i686的硬件上安装x86_64的Linux操作系统!这点得要牢记在心!

export: 自定义变量转成环境变量

回到顶部

用户登录Linux并取得bash之后,bash就是一个独立的进程,每个进程都有一个进程标识符PID标识该进程。 接下来在这个bash下运行的任何命令都是由这个bash进程衍生出来的,运行的命令就被称为子进程。 父进程与子进程相关性示意图如下:
父进程与子进程相关性示意图 如上所示,整个命令运作的环境是实线的部分,我们在原本的bash下执行另一个bash,结果操作的环境接口会跑到第二个bash去(就是子进程), 那原本的bash进入暂停sleep状态。 若要回到原本的bash, 就执行exit或logout退出第二个bash。
子进程仅会继承父进程的环境变量, 子进程不会继承父进程的自定义变量! 原本bash的自定义变量在进入子进程后就会消失不见,一直到你离开子进程并回到原本的父进程后,这个变量才会出现!
export命令可以将自定义变量变成环境变量,就可以让自定义变量继续在子进程中使用,:

              [peter@study ~]$ export 变量名称
            
这东西用在分享自己的变量设定给后来呼叫的文件或其他程序啦! 像常常在自己的主控文件后面呼叫其他附属文件(类似函式的功能),但是主控文件与附属文件内都有相同的变量名称, 若一再重复设定时,要修改也很麻烦,此时只要在原本的第一个文件内设定好 export 变量 , 后面所呼叫的文件就能够使用这个变量设定了! 而不需要重复设定,这非常实用于shell script当中喔!
export不接任何参数可以将所有的环境变量列出来:
              [peter@study ~]$ export
              declare -x HISTSIZE="1000"
              declare -x HOME="/home/peter"
              declare -x HOSTNAME="study.centos.peter"
              declare -x LANG="zh_TW.UTF-8"
              declare -x LC_ALL="en_US.utf8"
              ...
            
可以使用declare将环境变量转成自定义变量!

影响显示结果的语系变量 (locale)

回到顶部

当我们使用man command去查询某个数据的说明文件时,说明文件的内容可能会因为使用的语系不同而产生乱码。 另外,利用ls查询文件的时候,也可能会出现乱码的情况。
导致这些问题的原因其实就是语系的问题。
目前大多数的Linux distributions已经支持日渐流行的UTF-8了,也都支持大部分的国家语系。
locale命令可以查询当前Linux支持的语系:

              [peter@study ~]$ locale -a
              ....
              zh_CN
              zh_CN.big5 #big5中文编码
              zh_CN.euctw
              zh_CN.utf8 #utf8中文编码
              zu_ZA
              zu_ZA.iso88591
              zu_ZA.utf8
            
中文语系至少支持了两种以上的编码,一种是目前常见的big5,另一种则是越来越流行的utf-8编码。
那么我们如何修订这些编码呢?其实可以通过底下这些变量:
              [peter@study ~]$ locale #不加任何选项与参数
              LANG=en_US.UTF-8 #主语言的环境
              LANGUAGE=en_US
              LC_CTYPE="en_US.UTF-8" #字符(文字)辨识的编码
              LC_NUMERIC=zh_CN.UTF-8 #数字系统的显示讯息
              LC_TIME="en_US.UTF-8" #时间系统的显示数据
              LC_COLLATE="en_US.UTF-8" #字符串的比较与排序等
              LC_MONETARY=zh_CN.UTF-8 #币值格式的显示等
              LC_MESSAGES="en_US.UTF-8" #讯息显示的内容,如菜单、错误讯息等
              LC_PAPER=zh_CN.UTF-8
              LC_NAME=zh_CN.UTF-8
              LC_ADDRESS=zh_CN.UTF-8
              LC_TELEPHONE=zh_CN.UTF-8
              LC_MEASUREMENT=zh_CN.UTF-8
              LC_IDENTIFICATION=zh_CN.UTF-8
              LC_ALL= #整体语系的环境
            
可以逐一设定每个与语系相关的变量,但事实上,只需设定LANG或者是LC_ALL,其他的语系变量会被这两个变量所覆盖! 为什么在Linux主机的终端机接口(tty1 ~ tty6)的环境下, 如果设定LANG=zh_TW.utf8这个设定值生效后,使用man或者其他讯息输出时, 都会有一堆乱码,尤其是使用 ls -l 这个参数时?
因为在 Linux 主机的终端机接口环境下是无法显示像中文这么复杂的编码文字, 所以就会产生乱码了。
也就是如此,我们才会必须要在tty1 ~ tty6的环境下, 加装一些中文化接口的软件,才能够看到中文! 不过,如果你是在 MS Windows 主机以远程联机服务器的软件联机到主机的话,文字接口确实是可以看到中文的。
此时反而你得要在 LC_ALL 设定中文编码才好呢! 无论如何,如果发生一些乱码的问题,那么设定系统里面保有的语系编码, 例如:en_US 或 en_US.utf8 等等的设定,应该就 OK 的啦!
好了,那么系统默认支持多少种语系呢?
当我们使用locale时,系统是列出目前Linux主机内保有的语系文件, 这些语系文件都放置在: /usr/lib/locale/ 这个目录中。
你当然可以让每个使用者自己去调整自己喜好的语系,但是整体系统默认的语系定义在哪里呢?
其实就是在 /etc/locale.conf 啰!这个文件在 CentOS 7.x 的内容有点像这样:
              [peter@study ~]$ cat /etc/locale.conf
              LANG=zh_TW.utf8
              LC_NUMERIC=zh_TW.UTF-8
              LC_TIME=zh_TW.UTF-8
              LC_MONETARY=zh_TW.UTF-8
              LC_PAPER=zh_TW.UTF-8
              LC_MEASUREMENT=zh_TW.UTF-8
            
你也可以自行将他改成你想要的语系编码即可。
假设你有一个纯文本文件原本是在 Windows 底下建立的,那么这个文件默认可能是big5 的编码格式。
在你将这个文件上传到 Linux 主机后,在 X window 底下打开时,咦!怎么中文字通通变成乱码了?
Linux目前大多默认是UTF-8显示!你只要将开启该文件的软件编码由utf8改成big5就能够看到正确的中文了!
原本是中文语系,所有显示的数据通通是中文。但为了网页显示的关系,需要将输出转成英文(en_US.utf8)的语系来展示才行。
但又不想要写入配置文件!毕竟是暂时显示用的~那该如何处理?
其实不很难,重点是LANG及LC_ALL而已!但在CentOS 7当中,你要让LC_ALL生效时,得要使用export转成环境变量才行:
              [peter@study ~]$ locale
              LANG=zh_TW.UTF-8
              LC_CTYPE="zh_TW.UTF-8"
              LC_NUMERIC="zh_TW.UTF-8"
              LC_TIME="zh_TW.UTF-8"[peter@study ~]$ LANG=en_US.utf8; locale
              [peter@study ~]$ export LC_ALL=en_US.utf8; locale
            

上一篇文章《什么是linux命令》提到,linux在执行命令之前,其实是经由shell来处理的。Shell等待用户的终端输入,根据用户输入的命令名字, 在环境变量PATH指定的路径下寻找和命令名字匹配的可执行文件,然后创建子进程,该子进程会被linux内核调度执行,在子进程中加载可执行文件并执行。 这就是一条命令执行的一个完整过程。当然这里面涉及的细节,不是一两篇文章可以说的清楚的。后面我会在不同的文章中说明这些细节。shell作为用户和linux内核沟通的桥梁, 可以认为shell是linux内核与用户沟通的默认官方代理人,有很多种不同版本的shell,最常见的是bash shell。所以当我们提到shell的时候,默认就是指的bash shell。 如果你对这个代理人不满意,也可以更换代理人。你甚至可以把python解释器或者perl解释器作为linux的shell。不过几乎没有人会对bash shell不满意,也很少有人这么做。 Bash作为linux的官方默认代理人已是历史约定。这么说只是让大家理解shell,它并没有那么神秘,和其他的脚本解释器,甚至我们敲的命令都是一样的,你可以在shell里再执行shell, 像这样直接敲:bash,就会进入一个新的shell。至于怎么更换默认的shell,这不是这篇文章的内容。以后我会另外一篇文章说明怎么更换默认的shell。
我们回到本文的主题,上面提到shell是去环境变量PATH指定的路径中寻找命令,那么什么是环境变量呢?简单来讲,环境变量是用来设置linux应用程序运行环境的变量。 我们经常接触的环境变量有PATH、HOME、HISTSIZE、HOSTNAME、PS1、PS2等。在shell命令行下可通过“echo $环境变量名”打印环境变量的值, 也可通过env或者export命令查看系统中已有的环境变量。环境变量配置的是应用层的环境,由应用层程序设置并使用。不同的应用程序会关注不同的环境变量。 比如很多时候PATH只会由shell关注,我们经常会配置的java环境变量,会由java虚拟机关注,其他的应用程序是不会关注的。如果你在命令行下只敲了cd,不带路径名, 那么cd就会关注HOME这个环境变量,并切换到HOME环境变量指定的路径中,这个就是登录用户的家目录了。不过cd是shell的内置命令,所以HOME也主要是shell关注的。 我最开始理解环境变量的时候,总是认为它们是内核的一部分,其实环境变量和内核没有多大的关系。环境变量并不是用来配置linux内核的。如果你想对linux内核进行配置, 可以在内核编译配置阶段通过make menuconfig配置或者在内核运行过程中通过sysctl命令配置。
环境变量是用来配置应用程序运行环境的变量,所以环境变量是和应用程序息息相关的,而应用程序在运行阶段是以linux进程的形式存在的,每个进程都有自己的环境变量, 那么这些环境变量存放在哪里呢?他们是从哪里来的呢? 这里我们需要从进程的虚拟地址空间布局着手。每个linux进程(shell也是一个linux进程)都有自己独立的进程虚拟地址空间,进程虚拟地址空间分为内核空间和用户空间, 通常所有的linux进程虚拟地址空间的内核空间896M以内的空间会直接线性映射到物理内存的0-896M空间内。为什么是896M,这也是历史条件下形成的。 剩下的虚拟地址空间内核会按照自己的分页机制,间接的映射到物理内存中。关于这部分内容就是linux内核的内存管理部分了,本文不作深入讨论。 后面会专门抽出时间写一篇内存管理的文章。
说的环境变量,为什么会扯到进程虚拟地址空间呢?因为我们的环境变量是以环境变量表(数组)的形式存放在进程虚拟地址空间的用户空间里。这也是我上面说环境变量和内核没什么关系的原因。 进程虚拟地址空间的布局图如下图所示:
Linux内核在内存中的布局 进程虚拟空间布局 这张进程虚拟内存布局图是本人所画的《linux内核原理大图》的局部。该图目前还在创作中,已完成大部分。关注本头条号,可随时关注本人最新文章。 图中深红色的gap上方为内核空间,gap下方为用户空间。内核空间和用户空间有gap隔开。这也是内核的一种保护机制。这张内存布局图非常重要, 在linux下学习c/c++编程的人应该对这张图比较熟悉,因为不管是c/c++还是linux内核,原理性的东西都是围绕着这张图展开的。这张图毫不夸张的说, 就是打开linux环境下c/c++基础编程的金钥匙。
在用户空间的最上部,stack和gap中间,就是环境变量表所存放的位置了。我们的进程如果要想获取环境变量或者设置环境变量,都是从这个内存区域获取和设置的。 该部分会通过内核分页机制映射到物理内存中。
那么这里的环境变量又是从哪里来的呢?在《什么是linux命令》一文中,我们指出,linux运行一条命令,就会创建出一个进程,而这个进程是由shell创建的。 也就是你在linux下执行的任何命令所创建的进程,都是shell的子进程。所以一个进程的环境变量,大部分都是从shell进程继承而来的。 子又生孙,孙又生子,子子孙孙无穷尽也,而山不在高...
那么shell进程的环境变量又是从哪里来的呢? 我们前面说过,shell作为linux内核与用户打交道的代理人,所以要面对各型各色的人, 每个人都有不同的喜好。我们上文提到,可能会有人在小角落里密谋想要替换掉bash shell,Shell为了留住大家的心,适应不同人的喜好,就允许每个用户设置自己的环境变量。所以shell的环境变量其实就是用户自己设置的。 所以我们经常说环境变量的时候,前面总是加上shell,叫shell环境变量。每次shell启动的时候,都会读取一系列的环境配置文件。 将环境配置文件中环境变量的值读到自己内存空间的位置, 也就是上图所说的位置啦。然后由shell生成的子进程就会继承shell的环境变量。那么shell(我们这里主要说的bash)的环境变量配置文件存放在哪里呢? 有哪些设置环境变量的方法呢?我知道如果文章写的太长,就没有人会有耐心看下去的,所以我会在下一篇文章中说明shell环境变量的配置。因为平时还要养家糊口, 我的出文速度有点慢,所以机智的你,在还没等到我的文章之前,就已经找到答案了。那么这篇文章也算起到抛砖引玉的作用了。
我们在知道一样东西的来龙去脉后,是不是学习起来就更加顺手了呢。当然还是那句话,你知道的越多,不知道的也越多。这边文章又挖了很多坑, 欢迎关注本头条号,我们一起挖坑,一起填坑。本人水平有限,如果文章有误,欢迎批评指正。

bash环境配置文件

回到顶部

上面提到的在bash命令行设置的命令别名、自定义变量等,在退出bash后都会消失。 如果想要保留这些设置,在下次启动bash的时候还能生效,那么就需要将这些变量写入bash的配置文件中。 bash的配置文件分为对整个系统生效的全局配置文件和仅对用户个人生效的用户配置文件。 bash在启动的时候,会按照顺序读取相应的配置文件。
在介绍bash配置文件前,一定要先理解login shell与non-login shell:
login shell:通过登录流程获取的shell称为login shell。例如通过tty1 ~ tty6登录系统,需要输入账号与密码,此时获取的bash就称为login shell;
non-login shell:不需要再次登录取得bash称为non-login shell。例如以下情况:
(1)通过X window图形界面登录Linux后,再以X的图形化接口启动一个terminal工具,终端接口并没有需要再次的输入账号与密码,此时获取的bash的就称为non-login shell了。
(2)在bash下再次运行bash命令,也不需要没有输入账号密码, 此时获取的bash子进程也是non-login shell。
这两种bash在启动的时候读取的配置文件并不一样。
一般来说,login shell只会读取两个配置文件:

1. /etc/profile:这是系统整体的设置,最好不要修改这个文件;
2. ~/.bash_profile或~/.bash_login或~/.profile:属于用户个人设置的配置文件!
下面就来了解这两个文件。
/etc/profile (login shell才会读)
可以用vim阅读一下该文件的内容。这个配置文件可以利用用户的标识符(UID)来设置很多重要的变量, 这也是每个登录用户获取bash时都会读取的配置文件! 所以如果想要为所有用户设置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会依序调用下面的文件:
/etc/profile.d/*.sh
所有在/etc/profile.d/目录内且扩展名为.sh的文件,只要用户具有r的权限, 就会被/etc/profile调用。
在CentOS 7.x中,/etc/profile.d/目录下的文件设置了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文件调用!
login shell模式的bash读取的整体环境配置文件其实只有/etc/profile,/etc/profile会调用其他的配置文件来设置bash的整体环境!

~/.bash_profile (login shell才会读)
bash在读完了整体环境设置的/etc/profile并藉此调用其他配置文件后,接下来会读取用户的个人配置文件。
在login shell的bash环境中,所读取的个人偏好配置文件其实主要有三个,依序分别是:
1. ~/.bash_profile
2. ~/.bash_login
3. ~/.profile
其实login shell的bash只会读取上面三个文件中的一个, 而读取的顺序则是依照上面的顺序。
也就是说,如果 ~/.bash_profile 存在,那么其他两个文件不论有无存在,都不会被读取。
如果~/.bash_profile不存在才会去读取~/.bash_login;
前两者都不存在才会读取~/.profile。
会有这么多的文件,其实是因应其他 shell 转换过来的用户的习惯而已。
查看peter家目录下的/home/peter/.bash_profile文件:

              [peter@study ~]$ cat ~/.bash_profile
              # .bash_profile
              # Get the aliases and functions
              if [ -f ~/.bashrc ]; then 判断并读取~/.bashrc文件
              . ~/.bashrc
              fi
              # User specific environment and startup programs #底下这几行为用户个性化设置
              PATH=$PATH:$HOME/.local/bin:$HOME/bin
              export PATH
            
可以看到这个文件内设置了PATH变量,而且还使用了export将PATH变成环境变量! 由于PATH在/etc/profile文件中已经设置过,所以在这里以累加的方式增加用户家目录下的~/bin/目录。 这样就可以将用户自己建立的可执行文件放到家目录下的~/bin/目录! 就可以直接执行该可执行文件而不需要使用绝对/相对路径来执行该文件。
if ... then ... fi那段内容指的是判断家目录下的~/.bashrc文件是否存,若存在则读入~/.bashrc的设置。 bash配置文件的读入是通过一个命令source来读取的! 也就是说~/.bash_profile其实会再调用~/.bashrc的设置内容!

整个login shell配置文件的读取流程:
login shell配置文件读取流程 实线的的方向是主线流程,虚线的方向则是被调用的配置文件! CentOS的login shell环境下,最终被读取的配置文件是~/.bashrc文件!所以,可以将自己的偏好设置写入该文件。

source 与 .
source:读入环境配置文件
/etc/profile与~/.bash_profile都是在取得login shell的时候才会读取的配置文件, 如果将自己的偏好设置写入上述的文件后,通常都得先注销再登入后,该设置才会生效。 那么,能不能直接读取配置文件而不注销登入呢? 那就得要利用source这个命令了!

              [peter@study ~]$ source 配置文件名
              将家目录下的~/.bashrc文件中的设置读入目前的bash环境中,下面两个命令是一样的:
              [peter@study ~]$ source ~/.bashrc
              [peter@study ~]$ . ~/.bashrc
            
source或小数点(.)都可以将配置文件的内容读进当前shell的环境中! 例如修改了~/.bashrc,不需要注销,立即以source ~/.bashrc就可以将刚刚最新设置的内容读进当前的shell环境中! ~/bash_profile和/etc/profile的设置中, 很多都是利用到这个source(或小数点)的功能!

~/.bashrc (non-login shell 会读)
non-login shell的bash的环境配置文件又是什么呢? 当取得non-login shell时,bash仅会读取~/.bashrc!

              [root@initroot ~]# cat ~/.bashrc
              # .bashrc
              # User specific aliases and functions #用户的个人设置
              alias rm='rm -i'
              alias cp='cp -i'
              alias mv='mv -i'
              # Source global definitions
              if [ -f /etc/bashrc ]; then #整体的环境设置
              . /etc/bashrc
              fi
            
上面是root用户家目录下的数据, 如果是一般用户的 ~/.bashrc 会有些许不同。
CentOS 7.x还会主动的调用/etc/bashrc文件!/etc/bashrc设置以下的bash变量:
依据不同的UID设置umask的值;
依据不同的UID设置提示字符PS1变量;
调用/etc/profile.d/*.sh的设置。
/etc/bashrc是CentOS特有的(其实是Red Hat系统特有的),其他不同的distributions可能会放置在不同的文件名。 ~/.bashrc会调用/etc/bashrc及/etc/profile.d/*.sh,万一没有~/.bashrc(不小心删除了),那么你会发现bash提示字符可能会变成这个样子:
              -bash-4.2$
            
因为没有调用/etc/bashrc来设置PS1变量!这种情况不会影响bash的使用。 可以复制/etc/skel/.bashrc文件到家目录,再编辑一下想要的内容,使用source去调用~/.bashrc,命令提示字符就会回来!
其他相关配置文件
事实上还有一些配置文件可能会影响到bash的操作:
/etc/man_db.conf
该文件设置了man page的路径!执行man的时候,man会去设置的路径中查找man page!
如果以tarball的方式来安装软件程序,man page可能会放置在/usr/local/softpackage/man里头,softpackage是软件名称, 这个时候就得将该路径加到/etc/man_db.conf里头,这样执行man的时候才会找到相关的说明文件。
~/.bash_history
默认的情况下, 历史命令记录在该文件中!最大记录数与HISTFILESIZE变量有关。 每次登入bash后,bash会先读取这个文件,将所有的历史命令读入内存, 当我们登入bash后就可以查知上次登录使用过哪些命令了。
~/.bash_logout
注销退出bash时,bash会调用该文件。 默认的情况下,bash只是清掉屏幕的信息。 也可以将一些备份或者是其他你认为重要的工作写在这个文件中(例如清空暂存盘), 当离开Linux的时候,就可以执行该文件中的设置!

上一篇文章《什么是linux环境变量》一文中提到,shell在开机启动的时候会读取环境配置文件,将环境变量读取到自己进程的内存空间里, 然后由shell创建的子进程就会继承这个shell的环境变量。那么开机启动的时候shell会读取哪些配置文件呢?
总的来讲,shell读取的配置文件主要有两种: 一种是针对系统环境配置的/etc/environment文件和对所有用户生效的全局配置文件/etc/profile;
还有一种是针对不同用户设置的,只对当前登录用户生效的配置文件。这类文件在每个用户的home目录下以隐藏文件的形式存在。在不同的发行版中都是不一样的。 例如,在我的mint中是~/.profile,在centos中是~/.bash_profile文件。那么,在开机启动的时候,shell是怎么选择使用哪个配置文件的呢? 一般情况,shell会在用户home目录下,按照如下顺序检查三个文件:

  • 1.~/.bash_profile
  • 2.~/.bash_login
  • 3.~/.profile
shell只会读取遇到的第一个存在的文件。例如你的home目录下存在~/.bash_profile和~/.bash_login文件,那么shell只会读取~/.bash_profile, 而忽略~/.bash_login。
所以如果我们想对shell进行配置,如果你的配置对系统中的所有用户都生效,那么按照正常的逻辑只需要在/etc/profile文件中配置就好了,如果只想对某个用户生效, 那么就在该用户的home目录下修改上述三个配置文件中的一个,通常在mint下是~/.profile,在centos中是~/.bash_profile。
我在mint下查看/etc/profile文件是这样的:
              peter@peter-VirtualBox:~$ cat /etc/profile
              # /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
              # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).

              if [ "${PS1-}" ]; then
                if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
                  # The file bash.bashrc already sets the default PS1.
                  # PS1='\h:\w\$ '
                  if [ -f /etc/bash.bashrc ]; then
                    . /etc/bash.bashrc
                  fi
                else
                  if [ "`id -u`" -eq 0 ]; then
                    PS1='# '
                  else
                    PS1='$ '
                  fi
                fi
              fi

              if [ -d /etc/profile.d ]; then
                for i in /etc/profile.d/*.sh; do
                  if [ -r $i ]; then
                    . $i
                  fi
                done
                unset i
              fi
            
linux shell环境配置文件
/etc/profile
该文件比较简单,先是对PS1变量进行设置,根据不同的shell,不同的登录用户设置PS1。 如果系统bash不是/bin/sh,就会读取/etc/bash.bashrc文件,在该文件中配置PS1变量。 配置完ps1变量后,我们主要关注最后面的这部分代码:
              if [ -d /etc/profile.d ]; then
              for i in /etc/profile.d/*.sh; do
              if [ -r $i ]; then
              . $i
              fi
              done
              unset i
              fi
            
这部分代码会遍历/etc/profile.d目录,读取并执行该目录下所有的.sh文件。 所以刚才我说如果你想让配置对系统中所有的用户都生效,按照正常的逻辑只需要修改/etc/profile文件就好了。 其实这样做是有问题的,更好的方法是在/etc/profile.d目录下创建自己的配置文件。
我们再来看一下centos下的/etc/profile文件,该文件截图不方便,我将整个源码粘贴过来:
              [root@pozoko ~]# cat /etc/profile
              # /etc/profile
              # System wide environment and startup programs, for login setup
              # Functions and aliases go in /etc/bashrc
              # It's NOT a good idea to change this file unless you know what you
              # are doing. It's much better to create a custom.sh shell script in
              # /etc/profile.d/ to make custom changes to your environment, as this
              # will prevent the need for merging in future updates.

              pathmunge () {
              case ":${PATH}:" in
              *:"$1":*)
              ;;
              *)
              if [ "$2" = "after" ] ; then
              PATH=$PATH:$1
              else
              PATH=$1:$PATH
              fi
              esac
              }

              if [ -x /usr/bin/id ]; then
              if [ -z "$EUID" ]; then
              # ksh workaround
              EUID=`/usr/bin/id -u`
              UID=`/usr/bin/id -ru`
              fi
              USER="`/usr/bin/id -un`"
              LOGNAME=$USER
              MAIL="/var/spool/mail/$USER"
              fi

              # Path manipulation

              if [ "$EUID" = "0" ]; then
              pathmunge /usr/sbin
              pathmunge /usr/local/sbin
              else
              pathmunge /usr/local/sbin after
              pathmunge /usr/sbin after
              fi

              HOSTNAME=`/usr/bin/hostname 2>/dev/null`
              HISTSIZE=1000

              if [ "$HISTCONTROL" = "ignorespace" ] ; then
              export HISTCONTROL=ignoreboth
              else
              export HISTCONTROL=ignoredups
              fi

              export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL

              # By default, we want umask to get set. This sets it for login shell
              # Current threshold for system reserved uid/gids is 200
              # You could check uidgid reservation validity in
              # /usr/share/doc/setup-*/uidgid file

              if [ $UID -gt 199 ] && [ "`/usr/bin/id -gn`" = "`/usr/bin/id -un`" ]; then
              umask 002
              else
              umask 022
              fi

              for i in /etc/profile.d/*.sh /etc/profile.d/sh.local ; do
              if [ -r "$i" ]; then
              if [ "${-#*i}" != "$-" ]; then
              . "$i"
              else
              . "$i" >/dev/null
              fi
              fi
              done

              unset i
              unset -f pathmunge
            
在该文件中也主要是设置几个主要的环境变量PATH、USER、LOGNAME、MAIL、HOSTNAME、HISTSIZE、HISTCONTROL, 根据不同的用户权限设置文件掩码,在文件的最后,同样是遍历/etc/profile.d目录下的sh文件。
在文件的开头有如下注释:
              # It's NOT a good idea to change this file unless you know what you
              # are doing. It's much better to create a custom.sh shell script in
              # /etc/profile.d/ to make custom changes to your environment, as this
              # will prevent the need for merging in future updates.
            
意思是不建议我们直接修改该文件,最好是在/etc/profile.d/目录下建立自己的sh文件。 这样做的好处是,如果你在这个文件中做了修改,那么如果下次你对系统做了升级,你做的修改就会被覆盖掉。
我们再来查看下用户家目录下的配置文件,在mint下我的配置文件是~/.profile。该文件源码如下:
              # ~/.profile: executed by the command interpreter for login shells.
              # This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
              # exists.
              # see /usr/share/doc/bash/examples/startup-files for examples.
              # the files are located in the bash-doc package.
              # the default umask is set in /etc/profile; for setting the umask
              # for ssh logins, install and configure the libpam-umask package.
              #umask 022
              # if running bash
              if [ -n "$BASH_VERSION" ]; then
              # include .bashrc if it exists
              if [ -f "$HOME/.bashrc" ]; then
              . "$HOME/.bashrc"
              fi
              fi
              # set PATH so it includes user's private bin if it exists
              if [ -d "$HOME/bin" ] ; then
              PATH="$HOME/bin:$PATH"
              fi
              # set PATH so it includes user's private bin if it exists
              if [ -d "$HOME/.local/bin" ] ; then
              PATH="$HOME/.local/bin:$PATH"
              fi
              
该文件主要读取home目录下的.bashrc文件,然后如果你的目录下面有bin目录或者/.local/bin目录,就会将这两个目录添加到PATH环境变量中。 所以如果你有自己的程序需要运行,但是又不想将可执行文件放在系统目录下,就可以在自己的home目录下面建立bin目录或者/.local/bin目录, 然后将可执行文件放在这个目录下,就可以像执行命令一样运行你的测试程序了。
我们再来看一下该文件开头的注释部分:
              # ~/.profile: executed by the command interpreter for login shells.
              # This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
              # exists.
            
这句话的意思是~/.profile文件会由登录shell执行,如果~/.bash_profile 或者 ~/.bash_login存在,那么shell就不会读取该文件了。这也应证了我前面提到的shell对home目录下三个文件的处理顺序。
再来看一下centos下的~/.bash_profile文件:
              # .bash_profile
              # Get the aliases and functions

              if [ -f ~/.bashrc ]; then
              . ~/.bashrc
              fi

              # User specific environment and startup programs

              PATH=$PATH:$HOME/bin
              export PATH
            
该文件和mint下的文件一样,同样会读取home目录下的.bashrc文件,然后将home目录下的~/bin 目录追加到环境变量PATH中。 通过mint和centos两个发行版的对比,我们发现不管家目录下是什么文件,最后都会执行~/.bashrc文件。
所以如果你想对某个用户shell进行配置,也可以在~/.bashrc文件中进行配置。在该文件中所作的配置不需要重新登录就可以生效,只需要执行. ~/.bashr或者重新打开一个shell就可以了。这是为什么呢?我们在mint下打开~/.bashrc文件,最前面有这样一行注释:
~/.bashrc: executed by bash(1) for non-login shells.
我们之前在 ~/.profile文件中也见过类似的注释:
~/.profile: executed by the command interpreter for login shells.
~/.bashrc是由non-login shells执行,而~/.profile是由login shells执行。什么是non-login shells和login shells呢?其实很简单,通过登录后取得shell就是登录shell,比如开机启动登录后获取的就是登录shell,通过ssh远程登录获取的shell也是登录shell。 那什么是非登录shell呢?非登录shell就是你开机登录以后,自己手动启动的shell就是非登录shell了,比如在mint图形界面下,通过terminal打开的就是非登录shell了。 你在登录shell里面再次执行的shell,比如直接敲bash命令,这样进入的也是非登录shell。
上面的注释简单的理解就是:登录shell会执行所有的配置文件,非登录shell只会执行~/.bashrc文件。
这里既然提到了登录shell和非登录shell,这里就顺便再提一下交互式shell和非交互式shell,因为如果你仔细查看shell配置文件的话, 就会看到有些地方会提到交互式shell和非交互式shell,大部分情况下你只要能看到shell的提示符,可以敲命令,那么就是在shell的交互式模式下。
如果你执行了一个shell脚本,那么这个脚本就是在shell的非交互模式下运行的。既然是非交互式shell,所以那些你肉眼能看到效果的变量其实也就没必要配置了,比如PS1, 所以你会在配置文件的某个角落看到这样一句[-z "$PS1" ] && return,意思就是如果是非交互式shell,就直接撂挑子退出了,因为你都看不到,配置还有个毛用。
在不同的发行版下,~/.bashrc又会调用其他的文件,比较特殊的是centos下~/.bashrc又会执行/etc/bashrc文件。代码如下:
              # .bashrc
              # User specific aliases and functions

              alias rm='rm -i'
              alias cp='cp -i'
              alias mv='mv -i'

              # Source global definitions

              if [ -f /etc/bashrc ]; then
              . /etc/bashrc
              fi
            
这些文件限于篇幅就不继续往下追踪了,基本都是对几个变量还有一些命令别名的配置。有兴趣的大家可以顺着源码继续往下探索。 不过到这里,基本也算是把整个脉络梳理的差不多了。
通过上面对配置文件源码的分析,我们大概可以总结出shell读取配置文件的顺序如下(从左到右,从上到下):
/etc/profile--->/etc/profile.d/*sh--->/etc/locale.conf ~/.bash_profile || ~/.bash_login || ~/.profile --->~/.bashrc --->(仅限centos) /etc/bashrc
这些配置文件大部分都是配置系统变量,也有一些是配置命令别名的。这里要注意,我说系统变量,没有说系统环境变量,因为有些不一定是环境变量,比如PS1(诡异懵逼的笑)。 所以如果想配置环境变量只需要在/etc/profile.d/目录下建立自己的sh文件,然后写上自己的配置,这是针对所有用户的配置。 然后如果只是针对某个用户进行配置,就到这个用户的home目录下,在~/.bash_profile 、 ~/.bash_login、~/.profile、~/.bashrc任意一个文件中添加自己的配置就好了。 这里要注意的一点是,很多人在/etc/profile.d/目录下做了一些配置,但是在开机启动后并没有生效,这很有可能是后面的配置文件又重新对某个变量做了配置, 把之前的配置覆盖掉了。 比如在home目录下又对该变量进行了配置。
除了在上述提到的配置文件中配置环境变量,我们还可以直接在命令行中配置环境变量,直接在命令行中配置的环境变量,在重新开机后就会消失,只适合用于临时性的测试。 由于时间仓促,本篇文章并没有手把手教你怎么配置环境变量,只是和大家一起缕清楚了在哪里可以配置环境变量,也没有说明变量和环境变量(export)的区别。 如果有没有说明白的地方,欢迎关注本人公众号,也可留言讨论。本人水平有限,欢迎批评指正。

文件的格式化与相关处理

回到顶部

接下来让我们来将文件进行一些简单的编排吧!底下这些动作可以将你的信息进行排版的动作, 不 需要重新以 vim 去编辑,通过数据流重定向配合底下介绍的 printf 功能,以及 awk 命令, 就可以 让你的信息以你想要的模样来输出了!

格式化打印printf

回到顶部

在很多时候,我们可能需要将自己的数据给他格式化输出的! 举例来说,考试卷分数的输出,姓名 与科目及分数之间,总是可以稍微作个比较漂亮的版面配置吧? 例如我想要输出底下的样式:

              Name Chinese English Math Average
              peter 80 60 92 77.33
              INITroot 75 55 80 70.00
              Ken 60 90 70 73.33
            
上表的数据主要分成五个字段,各个字段之间可使用 tab 或空格键进行分隔。 请将上表的资料转存 成为 printf.txt 文件名,等一下我们会利用这个文件来进行几个小练习的。 因为每个字段的原始数据长 度其实并非是如此固定的 (Chinese 长度就是比 Name 要多), 而我就是想要如此表示出这些数据, 此时,就得需要打印格式管理员 printf 的帮忙了! printf 可以帮我们将资料输出的结果格式化,而 且而支持一些特殊的字符~底下我们就来看看!
              [peter@study ~]$ printf '打印格式' 实际内容
            
选项与参数: 关于格式方面的几个特殊样式:
\a 警告声音输出
\b 退格键(backspace)
\f 清除屏幕 (form feed)
\n 输出新的一行
\r 亦即 Enter 按键
\t 水平的 [tab] 按键
\v 垂直的 [tab] 按键
\xNN NN 为两位数的数字,可以转换数字成为字符。
关于 C 程序语言内,常见的变数格式
%ns 那个 n 是数字, s 代表 string ,亦即多少个字符;
%ni 那个 n 是数字, i 代表 integer ,亦即多少整数字数;
%N.nf 那个 n 与 N 都是数字, f 代表 floating (浮点),如果有小数字数,
假设我共要十个位数,但小数点有两位,即为 %10.2f 啰!
接下来我们来进行几个常见的练习。假设所有的数据都是一般文字 (这也是最常见的状态),因此最常用来分隔数据的符号就是 [Tab] 啦! 因为 [Tab] 按键可以将数据作个整齐的排列!那么如何利用printf 呢?参考底下这个范例:
范例一:将刚刚上头数据的文件 (printf.txt) 内容仅列出姓名与成绩:(用 [tab] 分隔)
              [peter@study ~]$ printf '%s\t %s\t %s\t %s\t %s\t \n' $(cat printf.txt)
              Name Chinese English Math Average
              peter 80 60 92 77.33
              INITroot 75 55 80 70.00
              Ken 60 90 70 73.33
            
由于 printf 并不是管道命令,因此我们得要通过类似上面的功能,将文件内容先提出来给 printf 作 为后续的资料才行。 如上所示,我们将每个数据都以 [tab] 作为分隔,但是由于 Chinese 长度太长, 导致 English 中间多了一个 [tab] 来将资料排列整齐!啊~结果就看到资料对齐结果的差异了!
另外,在 printf 后续的那一段格式中,%s 代表一个不固定长度的字符串,而字符串与字符串中间就 以 \t 这个 [tab] 分隔符来处理!你要记得的是,由于 \t 与 %s 中间还有空格,因此每个字符串间 会有一个 [tab] 与一个空格键的分隔喔!
既然每个字段的长度不固定会造成上述的困扰,那我将每个字段固定就好啦!没错没错!这样想非常 好! 所以我们就将数据给他进行固定字段长度的设计吧!
范例二:将上述资料关于第二行以后,分别以字符串、整数、小数点来显示:
              [peter@study ~]$ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt | grep -v Name)
              peter 80 60 92 77.33
              INITroot 75 55 80 70.00
              Ken 60 90 70 73.33
            
上面这一串格式想必您看得很辛苦!没关系!一个一个来解释!上面的格式共分为五个字段, %10s 代表的是一个长度为 10 个字符的字符串字段,%5i 代表的是长度为 5 个字符的数字字段,至于那 个 %8.2f 则代表长度为 8 个字符的具有小数点的字段,其中小数点有两个字符宽度。我们可以使用 底下的说明来介绍 %8.2f 的意义:
字符宽度: 12345678
%8.2f 意义:00000.00
如上所述,全部的宽度仅有 8 个字符,整数部分占有 5 个字符,小数点本身 (.) 占一位,小数点下 的位数则有两位。 这种格式经常使用于数值程序的设计中!这样了解乎?自己试看看如果要将小数 点位数变成 1 位又该如何处理?
printf 除了可以格式化处理之外,他还可以依据 ASCII 的数字与图形对应来显示数据喔(注 3)! 举 例来说 16 进位的 45 可以得到什么 ASCII 的显示图 (其实是字符啦)?
范例三:列出 16 进位数值 45 代表的字符为何?
[peter@study ~]$ printf '\x45\n'
E
# 这东西也很好玩~他可以将数值转换成为字符,如果你会写 script 的话,
# 可以自行测试一下,由 20~80 之间的数值代表的字符是啥喔! ^_^
printf 的使用相当的广泛喔!包括等一下后面会提到的 awk 以及在 C 程序语言当中使用的屏幕输出, 都是利用 printf 呢!这里也只是列出一些可能会用到的格式而已,有兴趣的话,可以自行多作 一些测试与练习喔! ^_^
Tips
打印格式化这个 printf 命令,乍看之下好像也没有什么很重要的~ 不过,如果你需要
自行撰写一些软件,需要将一些数据在屏幕上头漂漂亮亮的输出的话, 那么 printf 可也是一个很棒的工具喔!

本章总结

回到顶部

正则表达式就是处理字符串的方法,他是以行为单位来进行字符串的处理行为;
正则表达式通过一些特殊符号的辅助,可以让使用者轻易的达到搜索/删除/取代某特定字符串的处理程 序;
只要工具程序支持正则表达式,那么该工具程序就可以用来作为正则表达式的字符串处理之用;
正则表达式与通配符是完全不一样的东西!通配符 (wildcard) 代表的是 bash 操作接口的一个功能, 但正 规表示法则是一种字符串处理的表示方式!
使用 grep 或其他工具进行正则表达式的字符串比对时,因为编码的问题会有不同的状态,因此, 你最好 将 LANG 等变量设定为 C 或者是 en 等英文语系!
grep 与 egrep 在正则表达式里面是很常见的两支程序,其中, egrep 支持更严谨的正则表达式的语法;
由于编码系统的不同,不同的语系 (LANG) 会造成正则表达式抓取资料的差异。因此可利用特殊符号如 [:upper:] 来替代编码范围较佳;
由于严谨度的不同,正则表达式之上还有更严谨的扩展正则表达式;
基础正则表达式的特殊字符有: *, ., [], [-], [^], ^, $ 等!
常见的支持正则表达式的工具软件有: grep , sed, vim 等等
printf 可以通过一些特殊符号来将数据进行格式化输出;
awk 可以使用字段为依据,进行数据的重新整理与输出;
文件的比对中,可利用 diff 及 cmp 进行比对,其中 diff 主要用在纯文本文件方面的新旧版本比对 patch 命令可以将旧版数据更新到新版 (主要亦由 diff 建立 patch 的补丁来源文件)
11.6 本章习题
( 要看答案请将鼠标移动到答: 底下的空白处,按下左键圈选空白处即可察看 )
情境模拟题一:通过 grep 搜索特殊字符串,并配合数据流重定向来处理大量的文件搜索问题。
o o 目标:正确的使用正则表达式;
前提:需要了解数据流重定向,以及通过子命令 $(command) 来处理文件名的搜索;
我们简单的以搜索星号 (*) 来处理底下的任务:
1.
利用正则表达式找出系统中含有某些特殊关键词的文件,举例来说,找出在 /etc 底下含有星号 (*) 的 文件与内容:
解决的方法必须要搭配通配符,但是星号本身就是正则表达式的字符,因此需要如此进行:

                [peter@study ~]$ grep '\*' /etc/* 2> /dev/null
你必须要注意的是,在单引号内的星号是正则表达式的字符,但我们要找的是星号,因此需要加上转义字符 (\)。
但是在 /etc/* 的那个 * 则是 bash 的通配符! 代表的是文件的文件名喔!不过由上 述的这个结果中,我们仅能找到 /etc 底下第一层子目录的数据,无法找到次目录的数据, 如果想 要连同完整的 /etc 次目录数据,就得要这样做:
                [peter@study ~]$ grep '\*' $(find /etc -type f ) 2> /dev/null
# 如果只想列出文件名而不要列出内容的话,使用底下的方式来处理即可喔!
                [peter@study ~]$ grep -l '\*' $(find /etc -type f ) 2> /dev/null
2. 但如果文件数量太多呢?如同上述的案例,如果要找的是全系统 (/) 呢?你可以这样做:
                [peter@study ~]$ grep '\*' $(find / -type f 2> /dev/null )
-bash: /usr/bin/grep: Argument list too long
真要命!由于命令列的内容长度是有限制的,因此当搜索的对象是整个系统时,上述的命令会发生
错误。那该如何是好? 此时我们可以通过管道命令以及 xargs 来处理。举例来说,让 grep 每次仅
能处理 10 个文件名,此时你可以这样想:
a. 先用 find 去找出文件;
b. 用 xargs 将这些文件每次丢 10 个给 grep 来作为参数处理;
c. grep 实际开始搜索文件内容。
所以整个作法就会变成这样:
                [peter@study ~]$ find / -type f 2> /dev/null | xargs -n 10 grep '\*'
3.
从输出的结果来看,数据量实在非常庞大!那如果我只是想要知道檔名而已呢?你可以通过 grep 的 功能来找到如下的参数!
                [peter@study ~]$ find / -type f 2> /dev/null | xargs -n 10 grep -l '\*'
情境模拟题二:使用管道命令配合正则表达式建立新命令与新变量。我想要建立一个新的命令名为 myip , 这个命令能够将我系统的 IP 抓取出来显示。而我想要有个新变量,变量名为 MYIP ,这个变量可以记录我 的 IP 。
处理的方式很简单,我们可以这样试看看:
1. 首先,我们依据本章内的 ifconfig, sed 与 awk 来抓取我们的 IP ,命令为:
                [peter@study ~]$ ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g'| sed 's/ *netmask.*$//g'
2. 再来,我们可以将此命令利用 alias 指定为 myip 喔!如下所示:
                [peter@study ~]$ alias myip="ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g'| \
> 3. sed 's/ *netmask.*$//g'
最终,我们可以通过变量设定来处理 MYIP 喔!
                [peter@study ~]$ MYIP=$( myip )
4. 如果每次登入都要生效,可以将 alias 与 MYIP 的设定那两行,写入你的 ~/.bashrc 即可!
简答题部分:
我想要知道,在 /etc 底下,只要含有 XYZ 三个字符的任何一个字符的那一行就列出来,要怎样进行?
                grep [XYZ] /etc/*
将 /etc/kdump.conf 内容取出后, (1)去除开头为 # 的行 (2)去除空白行 (3)取出开头为英文字母的那几行 (4) 最终统计总行数该如何进行?
                grep -v '^#' /etc/kdump.conf | grep -v '^$' | grep '^[[:alpha:]]' | wc -l
              

initroot编辑整理,转载请注明www.initroot.com