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

 更新时间:2023年08月04日 11:32:36   作者:Mochi_Cruise  
正常运行的shell脚本为啥有时会报错呢,这篇文章主要就是来和大家探索一下报错的原因以及解决方法,感兴趣的小伙伴可以跟随小编一起了解一下

1、话题引入

案例1

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

案例2

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

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

用不同的命令执行

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

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去执行脚本

#定义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

mochicruise@MochideMacBook-Pro Shell % echo $Shell
/bin/zsh

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

#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的位置

mochicruise@MochideMacBook-Pro Shell % which sh
/bin/sh

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

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

[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就不生效

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不生效的情况

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的区别

bashdash
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支持关联数组不支持数组,替代方法, 采用变量名+序号来实现类似的效果
字符串扩展支持parameter:offset:length,{parameter:offset:length},parameter:offset:length,{parameter:offset}不支持, 替代方法:采用expr或cut外部命令代替
大小写转换支持parameterpattern,{parameter^pattern},parameterpattern,{parameter^^pattern}和parameter,pattern,{parameter,pattern},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的包,内容完全一致

[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标准

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

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

    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。

到此这篇关于探究正常运行的shell脚本为啥有时会报错的文章就介绍到这了,更多相关shell脚本报错内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论