shebang(脚本声明)行过多-有什么方法可以减少行数?


12

我有一个包含约20个小.sh文件的项目。我将这些名称命名为“小”,因为通常没有文件包含超过20行的代码。我之所以采用模块化方法,是因为我忠于Unix理念,并且对我来说维护项目更容易。

在每个.sh文件的开头,我放置了#!/bin/bash

简而言之,我了解脚本声明有两个目的:

  1. 它们帮助用户回忆起执行该文件所需的外壳程序(例如,几年后不使用该文件)。
  2. 他们确保脚本仅在特定外壳程序(在这种情况下为Bash)下运行,以防止在使用其他外壳程序的情况下出现意外行为。

当一个项目开始从5个文件增长到20个文件或从20个文件增长到50个文件时(不是这种情况,只是为了演示),我们有20行或50 的脚本声明。我承认,即使对某些人来说可能很有趣,但我对使用20或50而不是每个项目只说1个(也许在项目主文件中)感到有点多余。

有没有一种方法可以通过在某些主文件中使用一些“全局”脚本声明来避免所谓的20或50或更多行的脚本声明冗余?


2
您不应该但可以;删除它并使用/bin/bash -x "$script"
jesse_b '18


...通常,到现在为止,我已经得出结论,该停止使用Shell脚本并获得一种实际的脚本语言来进行工作了。
Shadur

Answers:


19

即使您的项目现在可能仅由50个Bash脚本组成,但它迟早会开始累积用其他语言(如Perl或Python)编写的脚本(这是这些脚本语言所具有的好处,而Bash没有)。

如果#!每个脚本中没有合适的-line,那么在不知道要使用哪种解释器的情况下使用各种脚本将非常困难。是否从其他脚本执行每个脚本都没有关系,这只会将困难从最终用户转移到开发人员。这两类人都不需要知道脚本使用的语言才能使用。

在没有#!-line且没有显式解释器的情况下执行的shell脚本会根据不同的shell调用方式以不同的方式执行(请参见例如哪个shell解释器运行没有shebang的脚本,尤其是Stéphane的答案),这不是您想要的在生产环境中(您想要一致的行为,甚至可能希望具有可移植性)。

无论#!-line 怎么说,使用显式解释器执行的脚本都将由该解释器运行。如果您决定重新实现,例如使用Python或任何其他语言的Bash脚本,这将导致进一步的问题。

您应该花费这些额外的击键,并始终在#!每个脚本中添加一个-line。


在某些环境中,每个项目中的每个脚本中都有多段式样的法律文本。感到非常高兴的是,这只是一条#!在您的项目中感觉“多余”的线。


答案应该是这样扩展的:#!只要文件具有可执行权限,并且不是由解释器加载而是由Shell加载,shell就会通过行确定解释器。也就是说,/path/to/myprog.pl如果该#!行存在(指向perl解释器)并且文件具有exec许可,则运行perl程序。
rexkogitans

11

您误解了#!。实际上,这是操作系统的指令,可以在定义的解释器下运行该程序。

这样就可以编写脚本,#!/usr/bin/perl并且可以按原样运行结果程序,./myprogram并可以使用perl解释器。类似的#!/usr/bin/python将导致程序在python下运行。

因此,该行#!/bin/bash告诉OS在bash下运行该程序。

这是一个例子:

$ echo $0
/bin/ksh

$ cat x
#!/bin/bash

ps -aux | grep $$

$ ./x
sweh      2148  0.0  0.0   9516  1112 pts/5    S+   07:58   0:00 /bin/bash ./x
sweh      2150  0.0  0.0   9048   668 pts/5    S+   07:58   0:00 grep 2148

因此,尽管我的外壳是ksh,程序“ x”由于该#!行而在bash下运行,我们可以在进程列表中明确看到它。


ps -aux可能会发出警告,
Weiwei Zhou

@WeijunZhou说更多...
Kusalananda

某些版本ps会发出警告Warning: bad syntax, perhaps a bogus '-'?

2
ps支持BSDUXIX参数语法。 -aux错误的UNIX Stephen可能意味着aux这是正确的BSD
Jasen

1
人,两件事;(1)专注于示例概念(ps显示的调用bash)而不是显式语法;(2)ps -aux在CentOS 7和Debian 9上完美运行而没有警告;剪切粘贴只是我机器的输出;-是否需要的整个讨论适用于较早版本的linux procps,而不适用于当前版本。
Stephen Harris

9

.sh

不好的是.sh文件名的末尾。

想象一下,您重写了python中的脚本之一。

好了,现在您必须将第一行更改#!/usr/bin/python3为还不错,因为您还必须更改文件中的所有其他代码行。但是,您还必须将文件名从更改prog.shprog.py。然后,您必须在其他脚本中找到所有地方,以及所有用户脚本(您甚至不知道的用户脚本都存在),然后将其更改为使用新脚本。sed -e 's/.sh/.py/g'可能会对您认识的人有所帮助。但是现在您的修订控制工具正在显示许多无关文件的更改。

或者,以Unix方式执行此操作,prog而不给程序命名prog.sh

#!

如果您使用的是正确的#!,并将权限/模式设置为包括执行。然后,您无需了解口译员。计算机将为您完成此任务。

chmod +x prog #on gnu adds execute permission to prog.
./prog #runs the program.

对于非gnu系统,请阅读手册chmod(并学习八进制),也许仍然可以阅读。


1
嗯,扩展名不一定很糟糕,至少它们有助于与其他用户交流文件类型。
Sergiy Kolodyazhnyy

@SergiyKolodyazhnyy但是,您不需要了解该语言,只需要运行它即可。甚至Microsoft都在尝试远离扩展程序(不幸的是,我正在隐藏扩展程序)。但是,我同意它们可能是一个好主意:.image用于图片。.audio声音等等。因此告诉您它是什么,但不告诉您实现/编码。
ctrl-alt-delor

1
@ ctrl-alt-delor如果只对语言感到好奇,launch-missiles或者delete-project-and-make-manager-pissed我不想只运行该文件时调用文件:)
Sergiy Kolodyazhnyy

2
如果您对语言感到好奇,请使用file命令。它可以识别大多数文件类型。
Jasen

2
我同意扩展名对可执行脚本不利,正是出于给定的原因。我对需要由另一个Shell脚本(即非可执行脚本)来源的脚本使用.sh扩展名-在这种情况下,后缀为重要/有效,因为它告诉我们如何使用文件。
劳伦斯·伦肖

8

[hashbang行]确保脚本仅在特定外壳程序(在这种情况下为Bash)下运行,以防止在使用其他外壳程序的情况下出现意外行为。

可能值得指出,这是完全错误的。hashbang行绝不会阻止您尝试将文件提供给错误的shell /解释器:

$ cat array.sh
#!/bin/bash
a=(a b c)
echo ${a[1]} $BASH_VERSION
$ dash array.sh
array.sh: 2: array.sh: Syntax error: "(" unexpected

(或类似地awk -f array.sh等)


但是无论如何,模块化的另一种方法是在文件中定义外壳函数,如果需要,每个文件定义一个函数,然后source在主程序中定义文件。这样,您就不需要hashbang:它们将被视为其他任何注释,并且所有内容都将使用相同的shell运行。缺点是您需要source带有功能的文件(例如for x in functions/*.src ; do source "$x" ; done),并且它们都使用相同的shell运行,因此您无法用Perl实现直接替换其中的一个。


+1,例如bash数组。不熟悉多个shell或POSIX定义的某些内容的用户可能会假设一个shell的某些功能存在于另一个shell中。同样对于功能,这可能是相关的: unix.stackexchange.com/q/313256/85039
Sergiy Kolodyazhnyy

4

有没有一种方法可以通过在某些主文件中使用一些“全局”脚本声明来避免所谓的20或50或更多行的脚本声明冗余?

有-这被称为可移植性或POSIX合规性。力争始终以可移植的,不依赖外壳的方式编写脚本,仅从使用#!/bin/sh或当您/bin/sh进行交互使用的脚本(或至少是POSIX兼容的外壳,如ksh)中获取脚本。

但是,可移植脚本不能使您摆脱以下困扰:

  • 需要使用其中一个外壳的功能(ilkkachu的答案是一个示例)
  • 需要使用比Shell更高级的脚本语言(Python和Perl)
  • PEBKAC错误:你的脚本落入谁运行脚本不是用户J.Doe的手预期它,例如它们运行/bin/sh与脚本csh

如果我表达我的观点,那么通过消除“避免冗余” #!是错误的目标。目标应该是一致的性能,并且实际上应该是一个工作脚本,该脚本可以工作并考虑极端情况,并遵循某些一般规则,例如not-parsing-lsalways-quoting-variables-unless-word-splitting

它们帮助用户回忆起执行该文件所需的外壳程序(例如,几年后不使用该文件)。

它们实际上不是给用户的-它们是给操作系统运行适当的解释器的。在这种情况下,“正确”表示OS找到了shebang中的内容;如果下面的代码是错误的shell,那是作者的错。至于用户内存,我认为不需要Microsoft Word或Google Chrome之类的程序的“用户”就知道要编写哪种语言程序。作者可能需要。

再说一次,可移植的脚本可能甚至不需要记住您最初使用的shell,因为可移植脚本应该可以在任何POSIX兼容的shell中使用。

他们确保脚本仅在特定外壳程序(在这种情况下为Bash)下运行,以防止在使用其他外壳程序的情况下出现意外行为。

他们没有。实际上防止意外行为的是编写可移植脚本。


1

之所以需要将其放在#!/bin/bash每个脚本的顶部,是因为您将其作为一个单独的进程运行。

当您将脚本作为新进程运行时,操作系统会看到它是一个文本文件(与二进制可执行文件相对),并且需要知道要执行哪个解释器。如果您不告知,则操作系统只能使用其默认设置(管理员可以更改)进行猜测。因此,该#!行(当然还要加上可执行文件的权限)将一个简单的文本文件转换为可以直接运行的可执行文件,并确信操作系统知道如何处理该文件。

如果要删除该#!行,则选项为:

  1. 直接执行脚本(如您现在所做的那样),并 希望默认解释器与脚本匹配-您可能在大多数Linux系统上都是安全的,但不能移植到其他系统(例如Solaris)上,可以将其更改为任何东西(我什至可以将其设置为vim在文件上运行!)。

  2. 使用执行脚本bash myscript.sh。这可以正常工作,但是您必须指定脚本的路径,而且很乱。

  3. 使用提供源脚本. myscript.sh,该脚本在当前解释器进程(即,不是子进程)中运行脚本。但这几乎可以肯定需要修改您的脚本,并使它们的模块化/重用性降低。

在情况2和3中,文件不需要(也不应该具有)执行权限,并且给它一个.sh(或.bash)后缀是一个好主意。

但是这些选项都不适合您的项目,因为您说过要保持脚本模块化(=>独立且自包含),因此应将#!/bin/bash行放在脚本的顶部。

在您的问题中,您还说过您正在遵循Unix哲学。如果是这样,那么您应该了解该#!行的真正用途,并在每个可执行脚本中继续使用它!


感谢您的好答案。我喜欢在回答一切,以为upvoting直到这一点:then you should learn what the #! line is really for, and keep using it in every executable script!。一个人可以在一定的知识点上学习哲学。在所有这些答案之后,我理解了为什么可以将其包含在该术语中。我建议编辑答案,将其替换为“将shebang视为您尊重和采用的Unix哲学的内部部分”。
user9303970

1
抱歉,如果我是直接的话,或者该评论似乎不礼貌。您的评论建议您更喜欢在IDE中工作-使用“项目”,该项目可以为该项目中的脚本定义全局规则。这是一种有效的开发方法,但是与将每个脚本当作一个独立的独立程序(您所指的Unix哲学)并不合适。
劳伦斯·伦肖
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.