正常运行的shell脚本为啥有时会报错?

1、话题引入

案例1

在项目用java写过一套执行Shell脚本的逻辑,在一台centos7.5的服务器上正常运行,部署在一台ubuntu18.04的机器上却运行失败。

案例2

写一个脚本test.sh,使用不同的命令执行

bash 复制代码
mochicruise@MochideMacBook-Pro Shell % cat test.sh 
#!/bin/bash
echo "---------"
source 233
echo "error"
​

用不同的命令执行

markdown 复制代码
mochicruise@MochideMacBook-Pro Shell % ./test.sh 
---------
./test.sh: line 3: 233: No such file or directory
error
mochicruise@MochideMacBook-Pro Shell % sh test.sh 
---------
test.sh: line 3: 233: No such file or directory
mochicruise@MochideMacBook-Pro Shell % bash test.sh 
---------
test.sh: line 3: 233: No such file or directory
error

上面两个案例执行的结果不同,其原因是Shell类型及兼容模式不同,执行的结果也有不同

2、Shell类型

列举一些常见的Shell

sh(Bourne Shell):

Bourne Shell是Unix系统中最早的Shell之一,它的语法相对简单,功能较为基础。它是其他Shell的基础,许多脚本和系统工具仍然使用Bourne Shell语法。

bash(Bourne Again Shell):

bash是最常用的Shell之一,几乎在所有Linux发行版中都默认安装。它是Bourne Shell的增强版本,提供了更多功能和改进。bash具有强大的脚本编程能力,支持命令历史、自动补全、别名等特性。

dash(Debian Almquist Shell):

是一种轻量级的Shell,它是Bourne Shell的替代品,它遵循POSIX标准,旨在提供更快的启动速度和更低的内存消耗。dash主要用于Unix和Linux系统中的脚本执行,特别是在Debian和Ubuntu等发行版中作为默认的/bin/sh解释器。缺点是较少的扩展功能。

csh(C Shell):

C Shell是一种基于C语言语法的Shell,它引入了许多C语言的特性,如变量声明和控制结构。C Shell的语法更接近于C语言,因此对于熟悉C语言的开发人员来说更容易上手。

ksh(Korn Shell):

Korn Shell是由AT&T Bell实验室的David Korn开发的Shell,它结合了Bourne Shell和C Shell的特性,并添加了一些新功能。Korn Shell在功能和易用性方面都比较强大,是许多Unix系统的默认Shell。

tcsh(TENEX C Shell):

tcsh是C Shell的扩展版本,提供了更多的功能和改进。它增加了命令行编辑、命令补全、历史命令等特性,使得交互式使用更加方便。

zsh(Z Shell):

zsh可以说是Shell重的极品,它是bash和Korn Shell的扩展版本。它在自动补全、命令历史、插件和扩展、主题和提示符、通配符方面都很强大,它的强大功能和用户友好性使得命令行操作更加高效和愉快。

3、脚本执行

Linux命令行与Shell脚本编程大全 第4版》P217第11.2章中说"在创建 Shell 脚本文件时,必须在文件的第一行指定要使用的 Shell,格式如下: #!/bin/bash",其实这个不是必要的,即使不设置,也是可以执行的。

3.1、Sha-Bang 是什么

Sha-Bang 就是通常脚本开头的头两个字符"#!"连在一起的读音,在 Unix 术语中, # 号通常称为 sharp,hash 或 mesh;而叹号!则常常称为 bang。

一般说来,任何一个脚本程序都应以其为起始。它们就是脚本文件有执行权限就能被直接执行的秘密所在。"#!"是一个魔数(Magic,其值为 0x23,0x21),可执行文件在被读取的时候,内核通过这个特定的数字组合开头识别出这是一个需要运行解释器脚本,并且根据约定将其后的字符串在读到换行以前解释为该脚本需要的解释器所在路径。系统会按照路径调用解释器之后再把整个文本的内容传递给解释器。脚本内容如何解释,执行什么动作就交给了解释器。所以,Shell 脚本虽然是一个纯文本文件,依然可以正常执行。

可以在2014年版的 Advanced bash scripting guide(revision 10)中,找到 #! 的相关说明,其中,Chapter 2. Starting Off With a Sha-Bang 详细解释了 "#!/xxx" 的用法。

sha-bang 这个符号通常在 Unix 系统的脚本中第一行开头中写到,它指明了执行这个脚本文件的解释程序:

  • 如果脚本文件中没有 #! 这一行,那么它执行时会默认用当前 Shell 去解释这个脚本(即:$SHELL 环境变量)。
  • 如果 #! 之后的解释程序是一个可执行文件,那么执行这个脚本时,它就会把文件名及其参数一起作为参数传给那个解释程序去执行。
  • 如果 #! 指定的解释程序没有可执行权限,则会报错"bad interpreter: Permission denied"。如果#!指定的解释程序不是一个可执行文件,那么指定的解释程序会被忽略,转而交给当前的 Shell 去执行这个脚本。
  • 如果 #! 指定的解释程序不存在,那么会报错 "bad interpreter: No such file or directory"。注意:#!之后的解释程序,需要写其绝对路径(如:#!/bin/bash),它是不会自动到 $PATH 中寻找解释器的。
  • 当然,如果你使用 bash test.sh 这样的命令来执行脚本,那么#!这一行将会被忽略掉,解释器当然是用命令行中显式指定的 bash。
  • 使用 #!/usr/bin/env 这样的脚本解释器名称,可以实现在不同平台上都能正确找到解释器。
  • 如果脚本文件是以 UTF-8 的 BOM(0xEF 0xBB 0xBF) 开头的,那么 exec 函数将不会启动 sha-bang 指定的解释器来执行该脚本。因此,Linux 的脚本文件不应在文件开头包含 UTF-8 的 BOM。

3.2、脚本执行原理

用户可以通过Shell和操作系统交互,通俗的讲Shell就是一个解释器,当我们输入命令Shell就解释执行,Shell有很多版本,在命令行中输入一条命令可以查看当前正在使用的Shell

bash 复制代码
mochicruise@MochideMacBook-Pro Shell % echo $SHELL
/bin/zsh

Shell和操作系统的交互有两种方式

交互式(Interactive):

Shell的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive)。

批处理(Batch):

Shell还有一种执行命令的方式称为批处理(Batch),用户事先写一 个Shell脚本,其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一 行敲到Shell提示符下执行。

在Linux命令输入./ test.sh 时,我们在这个文本文件开头指定了bash为默认的解释器,因此当前的交互式Shell会fork一个子进程,用bash解释器的代码去替换(也就是exec),而这个文本文件被当作是命令行参数传给这个子bash,等这个子bash执行完就会到我们的交互式bash了。

3.3、./script.sh、sh script.sh 和 /bin/sh script.sh 的区别

3.3.1、执行./script.sh:

./script.sh 会优先判断脚本有没有制定Shell类型,如果指定的话会以指定的Shell作为解释器,test.sh第一行定义了#!/bin/bash,那./test.sh就会以bash去执行脚本。如果没有则会以当前环境的默认Shell去执行脚本

bash 复制代码
#定义sha-bang
mochicruise@MochideMacBook-Pro Shell % ./test.sh   
---------
./test.sh: line 3: 233: No such file or directory
error
mochicruise@MochideMacBook-Pro Shell % sed '1d' test.sh > test2.sh
#不定义sha-bang
mochicruise@MochideMacBook-Pro Shell % chmod u+x test2.sh 
mochicruise@MochideMacBook-Pro Shell % ./test2.sh 
---------
./test2.sh: line 2: 233: No such file or directory
#生成test3.sh 并将它的sha-bang定义#!/bin/zsh
mochicruise@MochideMacBook-Pro Shell % sed '1s/bash/zsh/' test.sh > test3.sh
mochicruise@MochideMacBook-Pro Shell % cat test3.sh 
#!/bin/zsh
echo "---------"
source 233
echo "error"
mochicruise@MochideMacBook-Pro Shell % chmod u+x test3.sh 
mochicruise@MochideMacBook-Pro Shell % ./test3.sh        
---------
./test3.sh: 3: source: not found
error

可以看到两个脚本执行的结果不一样,是因为test2.sh是以环境默认Shell执行的

在centos中默认为bash(Bourne Again Shell)的环境中,不定义sha-bang,也是打印出了"error";这个应该是zsh没有完全遵循POSIX标准

看一下当前环境变量的默认Shell

bash 复制代码
mochicruise@MochideMacBook-Pro Shell % echo $Shell
/bin/zsh

也可以用echo $0查看脚本本身的名字,前面说在命令行是Shell与操作系统交互的一种方式

sql 复制代码
#zsh前面带个"-"是表示当前是log Shell
mochicruise@MochideMacBook-Pro Shell % echo $0
-zsh
mochicruise@MochideMacBook-Pro Shell % zsh
mochicruise@MochideMacBook-Pro Shell % echo $0    
zsh
​
#在manpage的INVOCATION中有说明相关信息
INVOCATION
A  login Shell is one whose first character of argument zero is a -, or one started with the --login option.

如果在test.sh 中加入一行" echo $0"的话会输出test.sh脚本的名字。即执行./test.sh那么会输出一行" ./test.sh",如果执行"sh test.sh"那么会输出一行"'test.sh"。

3.3.2、执行sh script.sh

先用which 查看执行命令sh的位置

bash 复制代码
mochicruise@MochideMacBook-Pro Shell % which sh
/bin/sh

然后看是否有链接文件,当前系统是macOS Ventura 13.4 ,并没有指向其他文件,

bash 复制代码
mochicruise@MochideMacBook-Pro Shell % ls -al /bin/sh
-rwxr-xr-x  1 root  wheel  134000 Jun 15 18:08 /bin/sh

而在CentOS上sh最终指向了/usr/bin/bash

bash 复制代码
[root@ecs-135733 ~]# which sh
/usr/bin/sh
[root@ecs-135733 ~]# ll /usr/bin/sh
lrwxrwxrwx 1 root root 4 Feb 10  2022 /usr/bin/sh -> bash
[root@ecs-135733 ~]# which bash
/usr/bin/bash
[root@ecs-135733 ~]# ll /usr/bin/bash
-rwxr-xr-x 1 root root 964536 Nov 25  2021 /usr/bin/bash

用sh/bash/dash/zsh等执行脚本时,会用它最终指向的Shell执行脚本,指定的sha-bang就不生效

markdown 复制代码
mochicruise@MochideMacBook-Pro Shell % sh test.sh 
---------
test.sh: line 4: 233: No such file or directory
mochicruise@MochideMacBook-Pro Shell % bash test.sh 
---------
test.sh: line 3: 233: No such file or directory
error
mochicruise@MochideMacBook-Pro Shell % dash test.sh 
---------
test.sh: 3: source: not found
error

bash(Bourne Again Shell)和zsh(Z Shell)执行的test.sh最终会把"error"打印出来,而sh(Bourne Shell)和 执行的脚本则不会;那么从上面看sh最终指向bash,为什么执行结果和bash不一样?这个就是与是否遵循POSIX标准有关,下面会讲到

用dash和bash来验证sha-bang不生效的情况

perl 复制代码
mochicruise@MochideMacBook-Pro Shell % cat test4.sh 
#!/bin/bash
select var in {1,2,4,6}
do
echo $var
break
done
mochicruise@MochideMacBook-Pro Shell % bash        
The default interactive Shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
bash-3.2$ ./test4.sh 
1) 1
2) 2
3) 4
4) 6
#? 2
2
bash-3.2$ dash test4.sh 
test4.sh: 2: select: not found
test4.sh: 3: Syntax error: "do" unexpected

ubuntu默认的Shell是dash,作为两种常用的Shell,概述一下dash和bash的区别

bash dash
function 为关键字 没有
select var in list; do command; done 不支持, 替代方法:采用while+read+case来实现
echo {0..10} 支持{n..m}展开 不支持,替代方法, 采用seq外部命令
here string 支持 不支持, 替代方法:可采用here documents
>&word重定向标准输出和标准错误 当word为非数字时,>&word变成重定向标准错误和标准输出到文件word >&word, word不支持非数字, 替代方法: >word 2>&1; 常见用法 >/dev/null 2>&1
数组 支持数组, bash4支持关联数组 不支持数组,替代方法, 采用变量名+序号来实现类似的效果
字符串扩展 支持 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a r a m e t e r : o f f s e t : l e n g t h , {parameter:offset:length}, </math>parameter:offset:length,{parameter:offset} 不支持, 替代方法:采用expr或cut外部命令代替
大小写转换 支持 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a r a m e t e r p a t t e r n , {parameter^pattern}, </math>parameterpattern,{parameter^^pattern}和 <math xmlns="http://www.w3.org/1998/Math/MathML"> p a r a m e t e r , p a t t e r n , {parameter,pattern}, </math>parameter,pattern,{parameter,,pattern} 不支持

3.3.3、执行/bin/sh script.sh

这个要看sh的执行路径是否是/bin/sh,或者sh的执行路径的包是否和/bin/sh的包内容完全一致,如果完全一致,那么两者执行效果完全一样。

3.3.4、/bin/sh script.sh 和 /usr/bin/sh script.sh的区别

/bin/bash和/usr/bin/bash 都是来自bash的rpm的包,内容完全一致

perl 复制代码
[root@ecs-135733 mnt]# rpm -qf /bin/bash
bash-4.2.46-35.el7_9.x86_64
[root@ecs-135733 mnt]# rpm -qf /usr/bin/bash
bash-4.2.46-35.el7_9.x86_64
[root@ecs-135733 mnt]# diff /bin/bash /usr/bin/sh && echo 'no diff'
no diff

3.4、POSIX标准

下面这个文档讲述了启用POSIX模式后的差异:bash POSIX Mode

bash有三种方式使其遵守POSIX标准

  • 启动时在sha-bang后面增加--posix参数
  • 启动后,使用set -o posix指令
  • 以sh来启动

只要程序以sh的方式启动,就会遵循POSIX标准,与路径无关,在manpage中可以看到

vbnet 复制代码
    If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well.  When invoked as an interactive login Shell, or a non-interactive Shell with the --login option, it first attempts to read and execute commands from /etc/profile and ~/.profile, in that order.  The --noprofile option may be used to inhibit this behavior.  When invoked as an interactive Shell with the name sh, bash looks for the variable ENV, expands its value if it is defined, and uses the expanded value as the name of a file to read and execute.  Since a Shell invoked as sh does not attempt to read and execute commands from any other startup files, the --rcfile option has no effect.  A non-interactive Shell invoked with the name sh does not attempt to read any other startup files.  When invoked as sh, bash enters posix mode after the startup files are read. 
    When bash is started in posix mode, as with the --posix command line option, it follows the POSIX standard for startup files.  In this mode, interactive Shells expand the ENV variable and commands are read and executed from the file whose name is the expanded value.  No other startup files are read.

3.5、Shell类型选取

从一个交互式终端的角度来讲,zsh更为强大,也越来越受人们的欢迎,所以在命令行可以用zsh。而bash更加符合posix标准,因此bash更适合做脚本解释器写,所以在编写Shell脚本是还是写上#!/bin/bash。

相关推荐
Selina K3 小时前
shell脚本知识点记录
笔记·shell
Dangks3 天前
[运维] 服务器本地网络可用性检查脚本
linux·运维·服务器·shell·network·系统工具
DreamADream4 天前
Shell编程中关于用户操作报错`用户无法登录`
shell
江上清风山间明月7 天前
shell脚本编写注意细节 ==、=等的区别
bash·shell·注意·相等·细节·==·=
188_djh12 天前
# linux从入门到精通-从基础学起,逐步提升,探索linux奥秘(十六)--shell
linux·运维·bash·shell·shell入门·shell变量·linux入门到精通
RamboPan12 天前
Mac 使用脚本批量导入 Apple 歌曲
macos·自动化·shell·apple·script
I'm Jie13 天前
一站式学习 Shell 脚本语法与编程技巧,踏出自动化的第一步
linux·ssh·shell·shell脚本·shell编程
rainsc16 天前
当多核变单核:破解CPU核心神秘失踪的终极指南!
服务器·shell
一丝晨光16 天前
编程语言支持中文变量吗?三字符组是什么来源?为什么结构体要考虑对齐?如何确定语言使用的地址是不是物理地址?用户态应用程序如何获取变量的物理地址?
java·linux·c++·c·shell·结构体·虚拟地址
188_djh16 天前
# linux从入门到精通-从基础学起,逐步提升,探索linux奥秘(十七)--shell运算符
linux·运维·bash·shell·shell脚本附带参数·shell运算符·shell文件测试运算符