Bash脚本编程之脚本基础和bash配置文件
脚本基础
不严谨地说,编程语言根据代码运行的方式,可以分为两种方式:
- 编译运行:需要先将人类可识别的代码文件编译成机器可运行的二进制程序文件后,方可运行。例如C语言和Java语言。
 - 解释运行:需要一个编程语言的解释器,运行时由解释器读取代码文件并运行。例如python语言(解释器:/usr/bin/python)和shell脚本(解释器:/bin/bash)。
 
根据其是否调用OS上的其他应用程序来分来:
- 脚本语言(shell脚本):依赖于bash自身以及OS中的其他应用程序(例如:grep/sed/awk等)。
 - 完整编程语言:依赖自身的语法和其自身丰富的库文件来完成任务,对系统的依赖性很低,例如python、PHP等。
 
根据编程模型:
- 过程式:以指令为中心来组织代码,数据是服务于代码。像C语言和bash。
 - 对象式:以数据为中心来组织代码,围绕数据组织指令。其编程的过程一般为创建类(class,例如:人类),根据类实例化出对象(例如:阿龙弟弟),对象具有类的通用属性(例如人类有手有脚,那么阿龙弟弟也有),对象可以具备自己的方法(method,例如写博客)。像Java语言。
 
像C++和python是既支持面向对象又支持面向过程。
因此我们可以总结出:bash是一门解释运行的过程式脚本语言,而bash的脚本,是一种将自身的编程逻辑和OS上的命令程序堆砌起来的待执行文件。
在shell脚本中,第一行我们需要向内核传达我们这个脚本是使用哪种解释器(interpreter)来解释运行。形如:
#!/bin/sh
或者
#!/bin/bash
或者
#!/usr/bin/python
“#!”是固定的格式,叫做shebang或者hashbang,后面跟着的是解释器程序的路径。如果是/bin/bash那就是bash脚本,如果是/usr/bin/python那就是python脚本。
shebang是可以添加选项的,例如可以使得脚本在执行时为登录式(login)shell。
#!/bin/bash -l
bash脚本的文件的后缀名(亦称扩展名)一般为“.sh”,这个名称主要用于让人易识别用的,具体脚本在执行的时候使用什么解释器,与文件的后缀名无关。
脚本还需要具备执行权限。在执行的时候,需要使用相对路径或者绝对路径方可正确执行。
~]# cat alongdidi.sh
#!/bin/bash
...
~]# chmod a+x alongdidi.sh
~]# ./alongdidi.sh
~]# /PATH/TO/alongdidi.sh
如果直接键入脚本的名称来执行的话,bash会从内置命令、PATH等中寻找alongdidi.sh命令,如果我们的脚本当前路径不存在于PATH中,就会报错。
~]# alongdidi.sh
bash: alongdidi.sh: command not found...
脚本也可以没有shebang和执行权限,我们依然可以通过调用bash命令,将脚本作为bash的参数来执行,这样也是可以的。
~]# bash alongdidi.sh
脚本中存在的空白行会被忽略,bash并不会将空白行打印出来。
除了shebang(#!)这种特殊的格式,其余位置出现#,其后面的字符均会被认为是脚本注释。
Bash执行一个脚本,实际上是在当前shell下创建子shell来执行的。
配置文件
参考资料:
建议英文不好、bash新手直接参考骏马兄的博文来学习即可,直接跳过官网的参考资料。骏马兄本人也是基于官网学习并自己反复验证的,准确率应该很高,可放心。
无论我们直接通过连接至物理服务器/机器的物理设备(鼠标、键盘和显示器),还是我们通过SSH客户端(无论GUI或者CLI)连接至Linux服务器中。系统都会在我们所连接上的终端上启用一个bash,我们通过这个shell来与OS进行交互。
即使我们执行bash脚本,系统也会创建一个子bash来完成任务。
这些bash在启动的时候,就需要读取其配置文件,官方也将其称之为启动文件(startup files)。用于使bash在启动的时候读取这些文件并执行其中的指令来设置bash环境。
交互式和登录式
Bash需要判断自身是否具备交互式(interactive)和登录式(login)的特性来决定自己应该读取哪些配置文件。
判断shell是否为交互式有两种方法:
方法一:判断特殊变量“$-”是否包含字母i。bash还有其他的特殊变量,有兴趣的请参考Special Parameters (Bash Reference Manual)。
[root@c7-server ~]# echo $-
himBH
[root@c7-server ~]# cat alongdidi.sh
#!/bin/bash
echo $-
[root@c7-server ~]# bash alongdidi.sh
hB
方法二:判断变量“$PS1”是否为空。交互式登录会设置该变量,如果变量为空,则为非交互式,否则为交互式。PS1是Prompt String,提示符字符串的意思,在官网中它属于Bourne Shell的变量之一。
[root@c7-server ~]# echo $PS1
[\u@\h \W]\$
[root@c7-server ~]# cat alongdidi.sh
#!/bin/bash
echo $PS1
[root@c7-server ~]# bash alongdidi.sh [root@c7-server ~]#
判断shell是否为登录式,使用bash的内置命令shopt来查看。它和内置命令set一起都用于修改shell的行为。Modifying Shell Behavior (Bash Reference Manual)
[root@c7-server ~]# shopt login_shell
login_shell on
[root@c7-server ~]# cat alongdidi.sh
#!/bin/bash
shopt login_shell
[root@c7-server ~]# bash alongdidi.sh
login_shell off
[root@c7-server ~]# bash
[root@c7-server ~]# shopt login_shell
login_shell off
常见的bash启动方式
在此之前需要读者大概了解一下终端的概念,可参考【你真的知道什么是终端吗? - Linux大神博客】。
PS:最后还把Windows给黑了一下。确实感觉windows应该算不上多用户,以前维护Windows Server的时候,使用远程连接只能以超管用户连接上2或者3个而已,再多就不行了。目前也不晓得为什么,可能windows自身的限制如此。
1、通过Xshell客户端使用SSH协议登录。
伪终端。交互式,登录式。
[root@c7-server ~]# tty
/dev/pts/
[root@c7-server ~]# who am i
root pts/ -- : (192.168.152.1)
[root@c7-server ~]# echo $PS1; shopt login_shell
[\u@\h \W]\$
login_shell on
2、在图形界面下右击桌面打开的终端。
伪终端。交互式,非登录式。
[root@c7-server ~]# tty
/dev/pts/
[root@c7-server ~]# who am i
root pts/ -- : (:)
[root@c7-server ~]# echo $PS1; shopt login_shell
[\u@\h \W]\$
login_shell off
可通过设置终端的属性来使其变为登录式。

该图形界面,在CentOS 7上本身位于Ctrl+Alt+F1的虚拟终端上。
3、虚拟终端。
通过Ctrl+Alt+Fn来切换,n为正整数,该截图位于Ctrl+Alt+F2,这种叫虚拟终端。交互式,登录式。

4、su命令启动的bash。
不使用login选项的su。交互式,非登录式。
[root@c7-server ~]# su root
[root@c7-server ~]# echo $PS1; shopt login_shell
[\u@\h \W]\$
login_shell off
使用login选项的su。交互式,登录式。
-, -l, --login:使shell为login shell。
[root@c7-server ~]# su - root
Last login: Thu Dec :: CST on pts/
[root@c7-server ~]# echo $PS1; shopt login_shell
[\u@\h \W]\$
login_shell on
5、通过bash命令启动的子shell。
一定为交互式,是否登录式看是否带-l选项。
[root@c7-server ~]# echo $PS1; shopt login_shell
[\u@\h \W]\$
login_shell off
[root@c7-server ~]# exit
exit
[root@c7-server ~]# bash -l
[root@c7-server ~]# echo $PS1; shopt login_shell
[\u@\h \W]\$
login_shell on
6、命令组合时。
PS:这部分看不懂,来自骏马金龙。
这种情况下,登录式与交互式的情况继承于父shell。
[root@c7-server ~]# (echo $BASH_SUBSHELL; echo $PS1; shopt login_shell) [\u@\h \W]\$
login_shell on
[root@c7-server ~]# su
[root@c7-server ~]# (echo $BASH_SUBSHELL; echo $PS1; shopt login_shell) [\u@\h \W]\$
login_shell off
7、使用ssh命令远程执行命令。
非交互式,非登录式。这种方式,在官网叫做远程shell,Remote Shell Daemon。
[root@c7-server ~]# ssh localhost 'echo $PS1; shopt login_shell'
root@localhost's password: login_shell off
8、运行shell脚本。
通过bash命令运行。非交互式,是否登录式根据是否带-l选项。
[root@c7-server ~]# cat alongdidi.sh
#!/bin/bash
echo $PS1
shopt login_shell
[root@c7-server ~]# bash alongdidi.sh login_shell off
[root@c7-server ~]# bash -l alongdidi.sh login_shell on
文件具备执行权限后直接运行。非交互式,非登录式。
[root@c7-server ~]# ./alongdidi.sh login_shell off
如果shebang具备了-l选项,那么直接运行为非交互式、登录式。
通过不带-l选项的bash执行,依然是非交互式,非登录式。
也就是说是否为登录式,先看CLI中的bash是否带-l选项,再看shebang是否带-l选项。均为非交互式。
[root@c7-server ~]# cat alongdidi.sh
#!/bin/bash -l
echo $PS1
shopt login_shell
[root@c7-server ~]# ./alongdidi.sh login_shell on
[root@c7-server ~]# bash alongdidi.sh login_shell off
配置文件的加载方式
在bash中,加载配置文件的方式是通过读取命令来实现的,它们是bash的内置命令source和“.”。
source filename [arguments]
. filename [arguments]
注意这里是一个单独的小数点,是一个bash内置命令。如果有arguments的话就作为位置参数。
本质上是读取了文件并在当前的shell下执行文件中的命令。(不同于shell脚本的执行是需要创建子shell)
bash相关的配置文件,主要有这些:
/etc/profile
~/.bash_profile
~/.bashrc
/etc/bashrc
/etc/profile.d/*.sh
注意:这些配置文件,一般是都要求要具备可读取的权限才行(虽然对于root用户可能无所谓)
位于用户家目录下的配置文件,为用户私有的配置文件,只有对应的用户才会加载,可实现针对用户的定制。位于/etc/目录下的配置文件,可以理解为全局配置文件,对所有用户生效。
为了测试不同的bash启动场景会加载哪些文件,我们在上述文件的末尾处加上一句echo语句。注意,我们是在文件的末尾加的echo语句,bash脚本的执行是按顺序自上而下执行,位置很关键。
echo "echo '/etc/profile goes'" >>/etc/profile
echo "echo '~/.bash_profile goes'" >>~/.bash_profile
echo "echo '~/.bashrc goes'" >>~/.bashrc
echo "echo '/etc/bashrc goes'" >>/etc/bashrc
echo "echo '/etc/profile.d/test.sh goes'" >>/etc/profile.d/test.sh
1、只要是登录式(无论是否交互式)的bash:先读取/etc/profile,再依次搜索~/.bash_profile、~/.bash_login和~/.profile并仅加载第一个搜索到的且可读的文件。在bash退出时,读取~/.bash_logout。
在/etc/profile文件中,有读取指令:
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
判断/etc/profile.d/目录下的*.sh和sh.local文件是否存在且可读,如果是的话,则读取。红色粗体字的判断,是判断是否为交互式的bash,如果是的话在读取配置文件时输出STDOUT,否则不输出。
在CentOS 6中没有/etc/profile.d/sh.local文件,也没有加载该文件的指令。在CentOS 7上,这个文件也只有一行注释,以我蹩脚的英文水平,我猜应该是用来填写一些环境变量,可用于覆盖掉/etc/profile中的环境变量。
~]# cat /etc/profile.d/sh.local
#Add any required envvar overrides to this file, it is sourced from /etc/profile
对于root用户来说,由于存在~/.bash_profile文件且可读(在我的测试环境中,普通用户也具备有可读的~/.bash_profile),因此~/.bash_login和~/.profile就被忽略了。
在~/.bash_profile中,有读取指令:
PS:记得留意那段英文注释。
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
在~/.bashrc中,也有读取指令:
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
在/etc/bashrc中,虽然有读取指令,但是这部分指令是在非登录式的情况下才执行:
if ! shopt -q login_shell ; then # We're not a login shell
...
for i in /etc/profile.d/*.sh; do
if [ -r "$i" ]; then
if [ "$PS1" ]; then
. "$i"
else
. "$i" >/dev/null
fi
fi
done
...
fi
图示如下。按编号顺序,首先加载第一条,加载完再加载第二条。

我们来测试之前所述的几种bash启动场景来看看。注意,必须得是登录式的才行。因为我们这个小节讨论的是登录式的。
I. Xshell客户端,伪终端登录,交互式登录式。
/etc/profile.d/test.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
之所以后加载的先显示,那是因为我们的echo语句是添加在脚本的末尾,而读取后续配置文件是在脚本的中间段。
II. ssh远程登陆。交互式登录式。
[root@c7-server ~]# ssh localhost
root@localhost's password:
Last login: Fri Dec :: from 192.168.152.1
/etc/profile.d/test.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
III. 启动带有登录选项的子shell。
~]# bash -l
/etc/profile.d/test.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
IV. 登录式切换用户。
~]# su -l
Last login: Fri Dec :: CST from localhost on pts/
/etc/profile.d/test.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
V. 执行脚本时,带有登录选项。
[root@c7-server ~]# cat a.sh
#!/bin/bash -l
echo 'haha'
[root@c7-server ~]# ./a.sh
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
haha
[root@c7-server ~]# bash -l a.sh
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
haha
执行脚本属于非交互式,而在非交互式场景下读取/etc/profile.d/*.sh文件时,不会有输出。(在/etc/profile文件中有定义,可以翻上去看)
. "$i" >/dev/null >&
因此就不会输出:
/etc/profile.d/test.sh goes
注意,仅仅只是不输出而已,但是还是有加载了配置文件的,如果涉及到比如环境变量的设置等,还是会执行的。
2、交互式但非登录式的bash:读取~/.bashrc文件,不读取/etc/profile、~/.bash_profile、~/.bash_login和~/.profile。

对应的场景为不带登录选项的子bash创建或者su用户切换。
[root@c7-server ~]# bash
/etc/profile.d/test.sh goes
/etc/bashrc goes
~/.bashrc goes
[root@c7-server ~]# su
/etc/profile.d/test.sh goes
/etc/bashrc goes
~/.bashrc goes
3、非交互式非登录式的bash。
不加载任何的配置文件,尝试展开环境变量BASH_ENV(这个变量一般是存储了某些个配置文件的路径),若有值则加载对应的配置文件,行为如下:
if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
正常在编写和执行bash脚本时,都不会刻意加上登录选项,因此几乎所有的bash脚本的执行都属于这种情况。
存在一种非交互式非登录式的bash特例,不使用这种配置文件加载方式。看下一个例子。
4、非交互式非登录式的bash特例:远程shell(Remote Shell Daemon)。
加载方式如下图所示。

由于是非登录式的shell,因此在读取*.sh的时候不输出。
[root@c7-server ~]# ssh localhost echo 'Remote Shell Daemon'
root@localhost's password:
/etc/bashrc goes
~/.bashrc goes
Remote Shell Daemon
Bash脚本编程之脚本基础和bash配置文件的更多相关文章
- 脚本编程中的test、bash调试、变量计算、参数
		
脚本编程中的test.bash调试.变量计算.参数 1.文件测试 -e FILE:测试文件是否存在 -f FILE:测试文件是否为普通文件 -d FILE:测试路径是否为目录 -r FILE:测试当前 ...
 - bash脚本编程知识储备
		
bash脚本编程: 脚本程序:解释器解释执行: 首先得理清一些琐碎的知识点,我尽量把我所学的帮朋友一起梳理一下 编程环境:(我会在接下来的篇章,图文例子三结合的方式带大家一起学习) ...
 - Linux Shell脚本编程-语句控制
		
过程式编程语言bash脚本编程面向过程的编程 顺序执行:默认法则,按照顺序一条一条语句执行 选择执行:分支,条件判断,符合条件的分支予以执行 循环执行:将同一段代码反复执行有限次,所以循环必须有 ...
 - Bash脚本编程基础
		
为实现某个任务,将许多命令组合后,写入一个可执行的文本文件的方法,称为Shell脚本编程. 按照应用的Shell环境不同,可以将Shell脚本分为多种类型.其中最常见的是应用于Bash和Tcsh的脚本 ...
 - linux学习19 shell脚本基础-bash脚本编程基础及配置文件
		
一.shell脚本编程 1.编程语言的分类,根据运行方式 a.编译运行:源代码 --> 编译器(编译) --> 程序文件 C语言: b.解释运行:源代码 --> 运行时启动解释器,由 ...
 - shell脚本编程及bash特性
		
bash特性及bash脚本编程初步 终端,附着在终端的接口程序; GUI: KDE,GNome,Xfce CLI: /etc/shells bash的特性: 命令行展开: ~,{} 命令别名: ali ...
 - Bash脚本编程之变量与多命令执行
		
变量基础知识 程序由指令加数据所组成,而变量可以理解为数据来源的一种. 变量名可以理解为指向了某个内存空间的地址,对于变量的赋值可理解为向内存空间写入数据,对于变量的引用可理解为从内存空间读取数据. ...
 - Bash脚本编程之数组
		
数组简介 在bash脚本编程当中,变量是存储单个元素的内存空间:而数组是存储多个元素的一段连续的内存空间. 数组由数组名和下标构成,如下. ARRAY_NAME[SUBSCRIPT] 数组按照下标的类 ...
 - Bash脚本编程学习笔记08:函数
		
官方资料:Shell Functions (Bash Reference Manual) 简介 正如我们在<Bash脚本编程学习笔记06:条件结构体>中最后所说的,我们应该把一些可能反复执 ...
 
随机推荐
- Java 添加Word形状或图形
			
本文将介绍通过java编程在Word文档中添加形状(图形),包括添加单个图形.组合图形,以及格式化图形样式,如设置形状填充色.大小.位置.边框样式.边框颜色.边框粗细.图形旋转角度.图形文本环绕方式等 ...
 - centos7环境搭建一台mysql服务器启动多个端口
			
在一台服务器上启动多个mysql实例,分别用不同的端口号,因centos7版本安装mysql5.7后不存在mysqld_multi .mysqld_safe等命令,做踩坑总结 Mysql多实例实现的3 ...
 - day20191109spring
			
笔记: 1.Idea构建maven项目之web应用项目 src main java文件夹中定义 Java源程序 resources文件中定义 资源配置文件信息 test文件夹中定义 测试Java程序 ...
 - 从spring boot发邮件聊到开发的友好性
			
前些天帮一个朋友做网站,全站都是静态页面,唯一需要用到后端开发的是他需要一个留言板.传统的留言板一般都是提交后保存到数据库,然后提供一个后台的留言列表给管理人员看,我嫌麻烦,就决定留言提交到后台直接发 ...
 - 【前端】之jQuery基础知识
			
jQuery 简介 在项目中引入jQuery: 去jQuery官网下载jQuery包:jquery-3.2.1.min.js 将下载的jQuery包添加到项目目录中 在标签下添加jQuery引用:&l ...
 - 【Luogu P3174 】[HAOI2009]毛毛虫
			
前言: 虽然很多人和我想法一样 ,但我还是不要脸地写了这题解 题目: 链接 大意: 在一棵树上取一条最长链以及它所连接的结点总共的结点个数 思路: 取链: 用树形\(DP\)就可以轻而易举的解决这个问 ...
 - 本地搭建的gitbook添加导航折叠插件
			
如果有多个目录,Gitbook在浏览器上打开时,默认所有的目录都会打开,当目录比较多时,全部显示不利于阅读. 可以使用插件配置目录折叠,使得打开浏览器时这些目录默认是关闭的. 在执行gitbook i ...
 - 化鲲为鹏,我有话说 ,鲲鹏ARM架构的优势
			
首先我在想为什么会用到鲲鹏,我个人认为最重要的还是要掌握自主研发的能力,打破国外关键技术的封锁.鲲鹏芯片完全是华为于自主设计内核,华为云Kunpeng服务器关键计算芯片全自研,提供产品可持续供应能力. ...
 - 解决 Docker Hadoop ssh  "Connection to * closed".问题
			
Docker 最近很火, 可以快速轻量级地虚拟出多个node,所以打算在Docker中跑Hadoop伪分布式应用. 其实要做出个简单的版本倒是不难,主要在 建立ssh无密码登录本机时,出现刚登录上去, ...
 - VIM操作记录
			
=============================================== 2019/12/12_第1次修改 vr7jj ============================= ...