启动Linux桌面环境自带的终端模拟包,或者从 Linux 控制台登录后,便可以看到 Shell 命令提示符。看见命令提示符就意味着可以输入命令了。命令提示符不是命令的一部分,它只是起到一个提示作用。
不同的 Linux 发行版使用的提示符格式大同小异,例如在 CentOS 中,默认的提示符类似下面这样:
[mozhiyan@localhost ~]$
各个部分的含义如下:
[]
是提示符的分隔符号,没有特殊含义。mozhiyan
表示当前登录的用户,我现在使用的是 mozhiyan 用户登录。@
是分隔符号,没有特殊含义。localhost
表示当前系统的简写主机名(完整主机名是 localhost.localdomain)。~
代表用户当前所在的目录为主目录(home 目录)。如果用户当前位于主目录下的 bin 目录中,那么这里显示的就是bin
。$
是命令提示符。Linux 用这个符号标识登录的用户权限等级:如果是超级用户(root 用户),提示符就是#
;如果是普通用户,提示符就是$
。总结起来,Linux Shell 默认的命令提示符的格式为:
[username@host directory]$
或者
[username@host directory]#
Linux 系统是纯字符界面,用户登录后,要有一个初始登录的位置,这个初始登录位置就称为用户的主目录(home 目录)。超级用户的主目录为/root/
,普通用户的主目录为/home/用户名/
。
有的资料也称为“家目录”,“家”是 home 的直译,它们都是一个意思。
用户在自己的主目录中拥有完整权限,所以我们也建议操作实验可以放在主目录中进行。
我们使用 cd 命令切换一下用户所在目录,看看有什么效果。
[mozhiyan@localhost ~]$ cd demo
[mozhiyan@localhost demo]$ cd /usr/local
[mozhiyan@localhost local]$
仔细看,如果切换用户所在目录,那么命令提示符中会变成用户当前所在目录的最后一个目录(不显示完整的所在目录 /usr/ local/,只显示最后一个目录 local)。
有些命令不能在一行内输入完成,需要换行,这个时候就会看到第二层命令提示符。第二层命令提示符默认为>
,请看下面的例子:
[mozhiyan@localhost ~]$ echo "Shell教程"
Shell教程
[mozhiyan@localhost ~]$ echo "
> http://
> c.biancheng.net
> "
http://
c.biancheng.net
第一个 echo 命令在一行内输入完成,不会出现第二层提示符。第二个 echo 命令需要多行才能输入完成,提示符>
用来告诉用户命令还没输入完成,请继续输入。
echo 命令用来输出一个字符串。字符串是一组由" "
包围起来的字符序列,echo 将第一个"
作为字符串的开端,将第二个"
作为字符串的结尾。对于第二个 echo 命令,我们将字符串分成多行,echo 遇到第一个"
认为是不完整的字符串,所以会继续等待用户输入,直到遇见第二个"
。
命令提示符的格式不是固定的,用户可以根据自己的喜好来修改,将在下一节展开讲解。
Shell 通过PS1
和PS2
这两个环境变量来控制提示符的格式,修改PS1
和PS2
的值就能修改命令提示符的格式。
在修改 PS1 和 PS2 之前,我们先用 echo 命令输出它们的值,看看默认情况下是什么样子的:
[mozhiyan@localhost ~]$ echo $PS1
[\u@\h \W]\$
[mozhiyan@localhost ~]$ echo $PS2
>
Linux 使用以\
为前导的特殊字符来表示命令提示符中包含的要素,这使得 PS1 和 PS2 的格式看起来可能有点奇怪。下表展示了可以在 PS1 和 PS2 中使用的特殊字符。
字符 | 描述 |
---|---|
\a | 铃声字符 |
\d | 格式为“日 月 年”的日期 |
\e | ASCII 转义字符 |
\h | 本地主机名 |
\H | 完全合格的限定域主机名 |
\j | shell 当前管理的作业数 |
\1 | shell 终端设备名的基本名称 |
\n | ASCII 换行字符 |
\r | ASCII 回车 |
\s | shell 的名称 |
\t | 格式为“小时:分钟:秒”的24小时制的当前时间 |
\T | 格式为“小时:分钟:秒”的12小时制的当前时间 |
\@ | 格式为 am/pm 的12小时制的当前时间 |
\u | 当前用户的用户名 |
\v | bash shell 的版本 |
\V | bash shell 的发布级别 |
\w | 当前工作目录 |
\W | 当前工作目录的基本名称 |
\! | 该命令的 bash shell 历史数 |
\# | 该命令的命令数量 |
\$ | 如果是普通用户,则为美元符号$ ;如果超级用户(root 用户),则为井号# 。 |
\nnn | 对应于八进制值 nnn 的字符 |
\\ | 斜杠 |
\[ | 控制码序列的开头 |
\] | 控制码序列的结尾 |
注意,所有的特殊字符均以反斜杠\
开头,目的是与普通字符区分开来。您可以在命令提示符中使用以上任何特殊字符的组合。
【实例】通过修改 PS1 变量的值来修改命令提示符的格式:
[mozhiyan@localhost ~]$ PS1="[\t][\u]\$ "
[12:51:43][mozhiyan]$ PS1="[c.biancheng.net]\$ "
[c.biancheng.net]$
第一次修改后可以显示当前的时间和用户名,第二次修改后显示C语言中文网的域名。为了保留版权,证明该教程出自C语言中文网,后续文章中我经常会使用[c.biancheng.net]$
这种命令提示符,大家不要觉得奇怪。
遗憾的是,通过这种方式修改的命令提示符只在当前的 Shell 会话期间有效,再次启动 Shell 后将重新使用默认的命令提示符。
如果希望持久性地修改 PS1,让它对任何 Shell 会话都有效,那么就得把 PS1 变量的修改写入到 Shell 启动文件中,我们将在《十四、如何编写自己的Shell配置文件(配置脚本)?》一节中展开讨论。
几乎所有编程语言的教程都是从使用著名的“Hello World”开始的,出于对这种传统的尊重(或者说落入俗套),我们的第一个 Shell 脚本也输出“Hello World”。
打开文本编辑器,新建一个文本文件,并命名为 test.sh。
扩展名
sh
代表 shell,扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用php
好了。
在 test.sh 中输入代码:
#!/bin/bash
echo "Hello World !" #这是一条语句
第 1 行的#!
是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell;后面的/bin/bash
就是指明了解释器的具体位置。
第 2 行的 echo 命令用于向标准输出文件(Standard Output,stdout,一般就是指显示器)输出文本。在.sh
文件中使用命令与在终端直接输入命令的效果是一样的。
第 2 行的#
及其后面的内容是注释。Shell 脚本中所有以#
开头的都是注释(当然以#!
开头的除外)。写脚本的时候,多写注释是非常有必要的,以方便其他人能看懂你的脚本,也方便后期自己维护时看懂自己的脚本——实际上,即便是自己写的脚本,在经过一段时间后也很容易忘记。
下面给出了一段稍微复杂的 Shell 脚本:
#!/bin/bash
# Copyright (c) http://c.biancheng.net/shell/
echo "What is your name?"
read PERSON
echo "Hello, $PERSON"
第 5 行中表示从终端读取用户输入的数据,并赋值给 PERSON 变量。read 命令用来从标准输入文件(Standard Input,stdin,一般就是指键盘)读取用户输入的数据。
第 6 行表示输出变量 PERSON 的内容。注意在变量名前边要加上$
,否则变量名会作为字符串的一部分处理。
上节我们编写了一个简单的 Shell 脚本,这节我们就让它运行起来。运行 Shell 脚本有两种方法,一种在新进程中运行,一种是在当前 Shell 进程中运行。
在新进程中运行 Shell 脚本有多种方法。
Shell 脚本也是一种解释执行的程序,可以在终端直接调用(需要使用 chmod 命令给 Shell 脚本加上执行权限),如下所示:
[mozhiyan@localhost ~]$ cd demo #切换到 test.sh 所在的目录
[mozhiyan@localhost demo]$ chmod +x ./test.sh #给脚本添加执行权限
[mozhiyan@localhost demo]$ ./test.sh #执行脚本文件
Hello World ! #运行结果
第 2 行中,chmod +x
表示给 test.sh 增加执行权限。
第 3 行中,./
表示当前目录,整条命令的意思是执行当前目录下的 test.sh 脚本。如果不写./
,Linux 会到系统路径(由 PATH 环境变量指定)下查找 test.sh,而系统路径下显然不存在这个脚本,所以会执行失败。
通过这种方式运行脚本,脚本文件第一行的#!/bin/bash
一定要写对,好让系统查找到正确的解释器。
你也可以直接运行 Bash 解释器,将脚本文件的名字作为参数传递给 Bash,如下所示:
[mozhiyan@localhost ~]$ cd demo #切换到 test.sh 所在的目录
[mozhiyan@localhost demo]$ /bin/bash test.sh #使用Bash的绝对路径
Hello World ! #运行结果
通过这种方式运行脚本,不需要在脚本文件的第一行指定解释器信息,写了也没用。
更加简洁的写法是运行 bash 命令。bash 是一个外部命令,Shell 会在 /bin 目录中找到对应的应用程序,也即 /bin/bash。
[mozhiyan@localhost ~]$ cd demo
[mozhiyan@localhost demo]$ bash test.sh
Hello World !
这两种写法在本质上是一样的:第一种写法给出了绝对路径,会直接运行 Bash 解释器;第二种写法通过 bash 命令找到 Bash 解释器所在的目录,然后再运行,只不过多了一个查找的过程而已。
有些读者可能会疑问,你怎么知道开启了新进程?你有什么证据吗?既然如此,那我就来给大家验证一下吧。
Linux 中的每一个进程都有一个唯一的 ID,称为 PID,使用$$
变量就可以获取当前进程的 PID,$$
是 Shell 中的特殊变量。
首先编写如下的脚本文件,并命名为 check.sh:
#!/bin/bash
echo $$ #输出当前进程PID
然后使用以上两种方式来运行 check.sh:
[mozhiyan@localhost demo]$ echo $$
2861 #当前进程的PID
[mozhiyan@localhost demo]$ chmod +x ./check.sh
[mozhiyan@localhost demo]$ ./check.sh
4597 #新进程的PID
[mozhiyan@localhost demo]$ echo
$$ 2861 #当前进程的PID
[mozhiyan@localhost demo]$ /bin/bash check.sh
4584 #新进程的PID
你看,进程的 PID 都不一样,当然就是两个进程了。
这里需要引入一个新的命令——source 命令。source 是 Shell内置命令的一种,它会读取脚本文件中的代码,并依次执行所有语句。你也可以理解为,source 命令会强制执行脚本文件中的全部命令,而忽略脚本文件的权限。
source 命令的用法为:
source filename
也可以简写为:
. filename
两种写法的效果相同。对于第二种写法,注意点号.
和文件名中间有一个空格。
例如,使用 source 运行上节的 test.sh:
[mozhiyan@localhost ~]$ cd demo #切换到test.sh所在的目录
[mozhiyan@localhost demo]$ source ./test.sh #使用source
Hello World !
[mozhiyan@localhost demo]$ source test.sh #使用source
Hello World !
[mozhiyan@localhost demo]$ . ./test.sh #使用点号
Hello World ! [mozhiyan@localhost demo]$ . test.sh #使用点号
Hello World !
你看,使用 source 命令不用给脚本增加执行权限,并且写不写./
都行,是不是很方便呢?
我们仍然借助$$
变量来输出进程的 PID,如下所示:
[mozhiyan@localhost ~]$ cd demo
[mozhiyan@localhost demo]$ echo $$
5169 #当前进程PID
[mozhiyan@localhost demo]$ source ./check.sh
5169 #Shell脚本所在进程PID
[mozhiyan@localhost demo]$ echo $$
5169 #当前进程PID
[mozhiyan@localhost demo]$ . ./check.sh
5169 #Shell脚本所在进程PID
你看,进程的 PID 都是一样的,当然是同一个进程了。
作为初学者,你可能看不懂这些运行方式有什么区别,没关系,暂时先留个疑问吧,后续教程中我们会逐一讲解。
如果需要在新进程中运行 Shell 脚本,我一般使用bash test.sh
这种写法;如果在当前进程中运行 Shell 脚本,我一般使用. ./test.sh
这种写法。这是我个人的风格。
最后再给大家演示一个稍微复杂的例子。本例中使用 read 命令从键盘读取用户输入的内容并赋值给 URL 变量,最后在显示器上输出。
#!/bin/bash
# Copyright (c) http://c.biancheng.net/shell/
echo "What is the url of the shell tutorial?"
read URL
echo "$URL is very fast!"
运行脚本:
[mozhiyan@localhost demo]$ . ./test.sh
What is the url of the shell tutorial?
http://c.biancheng.net/shell/↙
http://c.biancheng.net/shell/ is very fast!
↙ 表示按下回车键。
Shell 是一个应用程序,它的一端连接着 Linux 内核,另一端连接着用户。Shell 是用户和 Linux 系统沟通的桥梁,我们都是通过 Shell 来管理 Linux 系统。
我们可以直接使用 Shell,也可以输入用户名和密码后再使用 Shell;第一种叫做非登录式,第二种叫做登录式。
我们可以在 Shell 中一个个地输入命令并及时查看它们的输出结果,整个过程都在跟 Shell 不停地互动,这叫做交互式。我们也可以运行一个 Shell 脚本文件,让所有命令批量化、一次性地执行,这叫做非交互式。
总起来说,Shell 一共有四种运行方式:
判断是否为交互式 Shell 有两种简单的方法。
1) 查看变量-
的值,如果值中包含了字母i
,则表示交互式(interactive)。
【实例1】在 CentOS GNOME 桌面环境自带的终端下输出-
的值:
[c.biancheng.net]$ echo $- himBH
包含了i
,为交互式。
【实例2】在 Shell 脚本文件中输出-
的值:
[c.biancheng.net]$ cat test.sh
#!/bin/bash
echo $-
[c.biancheng.net]$ bash ./test.sh
hB
不包含i
,为非交互式。注意,必须在新进程中运行Shell脚本(见第十一节:运行Shell脚本)。
2) 查看变量PS1
的值,如果非空,则为交互式,否则为非交互式,因为非交互式会清空该变量。
【实例1】在 CentOS GNOME 桌面环境自带的终端下输出 PS1 的值:
[mozhiyan@localhost]$ echo $PS1
[\u@\h \W]\$
非空,为交互式。
【实例2】在 Shell 脚本文件中输出 PS1 的值:
[c.biancheng.net]$ cat test.sh
#!/bin/bash
echo $PS1
[c.biancheng.net]$ bash ./test.sh
空值,为非交互式。注意,必须在新进程中运行 Shell 脚本。
判断 Shell 是否为登录式也非常简单,只需执行shopt login_shell
即可,值为on
表示为登录式,off
为非登录式。
shopt 命令用来查看或设置 Shell 中的行为选项,这些选项可以增强 Shell 的易用性。
【实例1】在 CentOS GNOME 桌面环境自带的终端下查看 login_shell 选项:
[c.biancheng.net]$ shopt login_shell
login_shell off
【实例2】按下Ctrl+Alt+Fn
组合键切换到虚拟终端,输入用户名和密码登录后,再查看 login_shell 选项:
[c.biancheng.net]$ shopt login_shell
login_shell on
【实例3】在 Shell 脚本文件中查看 login_shel 选项:
[c.biancheng.net]$ cat test.sh
#!/bin/bash
shopt login_shell
[c.biancheng.net]$ bash ./test.sh
login_shell off
要同时判断是否为交互式和登录式,可以简单使用如下的命令:
echo $PS1; shopt login_shell
或者
echo $-; shopt login_shell
1) 通过 Linux 控制台(不是桌面环境自带的终端)或者 ssh 登录 Shell 时(这才是正常登录方式),为交互式的登录 Shell。
[c.biancheng.net]$ echo $PS1;shopt login_shell
[\u@\h \W]\$
login_shell on
2) 执行 bash 命令时默认是非登录的,增加--login
选项(简写为-l
)后变成登录式。
[c.biancheng.net]$ cat test.sh
#!/bin/bash
echo $-; shopt login_shell
[c.biancheng.net]$ bash -l ./test.sh
hB
login_shell on
3) 使用由()
包围的组命令或者命令替换进入子 Shell 时,子 Shell 会继承父 Shell 的交互和登录属性。
[c.biancheng.net]$ bash
[c.biancheng.net]$ (echo $PS1;shopt login_shell)
[\u@\h \W]\$
login_shell off
[c.biancheng.net]$ bash -l
[c.biancheng.net]$
(echo $PS1;shopt login_shell)
[\u@\h \W]\$
login_shell on
4) ssh 执行远程命令,但不登录时,为非交互非登录式。
[c.biancheng.net]$ ssh localhost 'echo $PS1;shopt login_shell'
login_shell off
5) 在 Linux桌面环境打开终端时,为交互式的非登录 Shell。
无论是否是交互式,是否是登录式,Bash Shell 在启动时总要配置其运行环境,例如初始化环境变量、设置命令提示符、指定系统命令路径等。这个过程是通过加载一系列配置文件完成的,这些配置文件其实就是 Shell 脚本文件。
与 Bash Shell 有关的配置文件主要有 /etc/profile、~/.bash_profile、~/.bash_login、~/.profile、~/.bashrc、/etc/bashrc、/etc/profile.d/*.sh,不同的启动方式会加载不同的配置文件。
~
表示用户主目录。*
是通配符,/etc/profile.d/*.sh 表示 /etc/profile.d/ 目录下所有的脚本文件(以.sh
结尾的文件)。
Bash 官方文档说:如果是登录式的 Shell,首先会读取和执行 /etc/profiles,这是所有用户的全局配置文件,接着会到用户主目录中寻找 ~/.bash_profile、~/.bash_login 或者 ~/.profile,它们都是用户个人的配置文件。
不同的 Linux 发行版附带的个人配置文件也不同,有的可能只有其中一个,有的可能三者都有,笔者使用的是 CentOS 7,该发行版只有 ~/.bash_profile,其它两个都没有。
如果三个文件同时存在的话,到底应该加载哪一个呢?它们的优先级顺序是 ~/.bash_profile > ~/.bash_login > ~/.profile。
如果 ~/.bash_profile 存在,那么一切以该文件为准,并且到此结束,不再加载其它的配置文件。
如果 ~/.bash_profile 不存在,那么尝试加载 ~/.bash_login。~/.bash_login 存在的话就到此结束,不存在的话就加载 ~/.profile。
注意,/etc/profiles 文件还会嵌套加载 /etc/profile.d/*.sh,请看下面的代码:
for i in /etc/profile.d/*.sh ; do
if [ -r "$i" ]; then
if [ "${-#*i}" != "$-" ]; then
. "$i"
else
. "$i" >/dev/null
fi
fi
done
同样,~/.bash_profile 也使用类似的方式加载 ~/.bashrc:
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
如果以非登录的方式启动 Shell,那么就不会读取以上所说的配置文件,而是直接读取 ~/.bashrc。
~/.bashrc 文件还会嵌套加载 /etc/bashrc,请看下面的代码:
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
学习了《十三、Shell配置文件(配置脚本)的加载》一节,读者应该知道 Shell 在登录和非登录时都会加载哪些配置文件了。对于普通用户来说,也许 ~/.bashrc 才是最重要的文件,因为不管是否登录都会加载该文件。
我们可以将自己的一些代码添加到 ~/.bashrc,这样每次启动 Shell 都可以个性化地配置。如果你有代码洁癖,也可以将自己编写的代码放到一个新文件中(假设叫 myconf.sh),只要在 ~/.bashrc 中使用类似. ./myconf.sh
的形式将新文件引入进来就行了
使用 source 命令引入其它代码文件时有一些细节需要注意,我们将在后面展开讨论。
你曾经是否感到迷惑,Shell 是怎样知道去哪里找到我们输入的命令的?例如,当我们输入 ls 后,Shell 不会查找整个计算机系统,而是在指定的几个目录中检索(最终在 /bin/ 目录中找到了 ls 程序),这些目录就包含在 PATH 变量中。
当用户登录 Shell 时,PATH 变量会在 /etc/profile 文件中设置,然后在 ~/.bash_profile 也会增加几个目录。如果没有登录 Shell,PATH 变量会在 /etc/bashrc 文件中设置。
如果我们想增加自己的路径,可以将该路径放在 ~/.bashrc 文件中,例如:
PATH=$PATH:$HOME/addon
将主目录下的 addon 目录也设置为系统路径。假如此时在 addon 目录下有一个 getsum 程序,它的作用是计算从 m 累加到 n 的和,那么我们不用 cd 到 addon 目录,直接输入 getsum 命令就能得到结果。
在《一、六:Shell命令的本质到底是什么?》一节中我已经给出了 getsum 程序及其源代码,有兴趣的读者可以猛击这里下载。下载完成后请配置环境变量,然后输入如下的命令就可以得到结果:
[c.biancheng.net]$ getsum -s 1 -e 100
5050
-s
选项表示起始(start)数字,-e
选项表示终止(end)数字,以上命令用来计算从 1 累加到 100 的和。
在《九、修改Linux命令提示符》一节中我曾提到,修改 PS1 变量的值就可以修改命令提示符的格式,但是那个时候大家还不了解 Shell 启动文件,所以只能临时性地修改,并不能持久。
现在我们已经知道,在 ~/.bashrc 文件中修改 PS1 变量的值就可以持久化,每个使用 Shell 的用户都会看见新的命令提示符。
将下面的代码添加到 ~/.bashrc 文件中,然后重新启动 Shell,命令提示符就变成了[c.biancheng.net]$
。
PS1="[c.biancheng.net]\$ "