如何创建通用Shell命令以退出已执行或源脚本的脚本或从中返回脚本? [英] How can I create a generic shell command to exit or return from a script that is either executed or sourced?

查看:67
本文介绍了如何创建通用Shell命令以退出已执行或源脚本的脚本或从中返回脚本?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现一个命令,该命令假定是Bash附带的常规退出"和返回" shell内置程序的包装器(Bourne等人),该命令不受不兼容的困扰这些问题的原因在于,如果我使用"exit 1"结束错误级别为1的脚本,如果我提供该脚本,它将导致我所在的shell终止.

I am trying to implement a single command that I assume would be a wrapper for the normal 'exit' and 'return' shell builtins that come with Bash (Bourne, et al), a command that is not plagued by the incompatibility issues of these in that if I use 'exit 1' to end a script with error level 1, if I source that script it will cause the shell I am in to terminate.

同样,如果我使用return,则存在以下问题:a)它只会返回到调用函数,而不会在没有其他逻辑的情况下结束整个脚本的运行.b)如果我在主脚本中使用return并执行它,它将出错而不是终止一个级别.

Likewise, if I use return, it has the problems that: a) It will only return to the calling function, not end the whole script being run without additional logic. b) If I use return in the main script and execute it, it will error rather than terminating with a level.

我想出的最佳解决方案是:

The best solution I have come up with is this:

我编写的所有shell脚本无论如何都从此序言开始,以获得一些稳定的基本变量:

All shell scripts I write start with this preamble anyway to get some stable base variables:

# Core Functions and Alias definitions Used For Script Setup
getScriptFile () { basename "${BASH_SOURCE[0]}" ; } # This filename as executed
getScriptPath () { local f="${BASH_SOURCE[0]}" ; local p ; while test -L "${f}"
    do p="$( cd -P "$( dirname "${f}" )" && pwd )" ; f="$( readlink "${f}" )" &&
    f="${p}/${f}" ; done ; p="$( cd -P "$( dirname "${f}" )" && pwd )" ;
    printf "%s\n" "${p}" ; }
shopt -s expand_aliases
SCRIPTpath="$( getScriptPath ; )"
SCRIPTfile="$( getScriptFile ; )"

因此,现在我将其添加到其中:

So, now I add this to it:

testEq () { [ "${1}" = "${2}" ] ; } # True if there is equality
testMatch () { [[ ${1} =~ ${2} ]] ; } # True if ARG1 MATCHES the Extended RegEx in ARG2
testInt () { [ "${1}" -eq "${1}" ] 2>/dev/null ; }  # I know this is okay with bash, to test with bourne
testIntLt () { testInt "${1}" && testInt "${2}" && [ "${1}" -lt "${2}" ] ; }
testSourced () { local c="0" ; local m="${#BASH_SOURCE[@]}" ;
    until testEq "$( basename "${BASH_SOURCE[${c}]}" )" "${SCRIPTfile}" &&
    testMatch "${FUNCNAME[${c}]}" "source|main" &&
    testIntLt "${c}" "${m}" ; do ((c++)) ; done ; 
    if testEq "source" "${FUNCNAME[${c}]}" ; then return 0 ; 
    else return 1 ; fi ; } # True if calling script is sourced into environment
getValidTerminationCommand () { testSourced && printf "%s" "return" || printf "%s" "exit" ; }
alias finish='eval $( getValidTerminationCommand ; )'

...并且当我想突然完成脚本时,我使用了新的finish命令,即

...and when I want to finish a script abruptly, I use my new finish command, i.e.

$ finish 5  <-- to finish with an error level of 5 

现在,无论脚本是源代码还是已执行的脚本,我都得到5的退出代码.对于错误类型的退出"或返回"没有错误.

Now, regardless of if the script is sourced or executed, I get an exit code of 5. No errors for the wrong type of "exit" or "return" .

getScriptPath命令比较复杂的原因是要处理脚本文件,这些脚本文件可能位于符号链接下的文件系统路径中.

The reason I have the complication on the getScriptPath command is to deal with script files that might live on a filesystem path that is under symlinks.

testSourced复杂的原因是,即使正在测试源脚本的脚本是通过调用脚本来完成的,也需要测试工作,即它需要确保正在测试以下事实:本身是来源,而不是调用脚本.

The reason for the complication on testSourced is that the test needs to work even if the script that is testing if it is sourced is being done so by a calling script, i.e. it needs to make sure that it is testing the fact that that itself is sourced, not say a calling script.

现在,我知道执行此操作的正常方法如下:

Now, I know that the normal way of doing this is as simple as follows:

$ return 5 2>/dev/null || exit 5  # If I want to end a script with error level 5

存在的问题是我无法弄清楚如何包装它以对其进行参数化,因此我可以将其用作完成"或终止"命令,该命令将以该错误级别终止脚本,就像我对它进行别名一样,我可以在没有返回代码的情况下完成此操作,或者如果它可以运行,那么它将返回到调用函数/脚本(如果有的话).

Problem with that is that I cannot figure out how to wrap it to parameterize it so that I can use it as a 'finish' or 'terminate' command that will terminate the script with this error level as if I alias it, I can do this without a code for the return, or if it functionise it then it will just return to the calling function/script if it has one.

必须要有一种比我在上面的完成"实现中想出的方法更容易携带,更简单的方法!当然这是标准的事情吗?

There has got to be a portable, simpler way than what I came up with on my implementation of 'finish' above!?! Surely this is a standard thing to do?

有人解决过吗?我缺少明显的东西吗?

Anyone solved this before?? Am I missing something obvious?

只需确认一下,我想做的是:-单个命令立即终止脚本-如果执行脚本,则使用相同的命令,来源-如果在函数,主脚本主体或子函数中使用相同的命令-如果脚本是直接调用的或由另一个可能执行或派生的脚本调用或提供的,则其行为相同(或可能导致执行该脚本的n个执行级别).-用作Bash shell(尽可能使用Bourne)的标准命令,以便于携带.

Just to confirm, what I want to make is this: - single command to instantly terminate a script - same command if script is executed, sourced - same command if used in a function, the main script body, or a sub function - behave the same if the script is called or sourced either directly or by another script that may be executed or sourced, (or potentially n levels of execution leading up to the script that is being run). - use as standard commands to the Bash shell (Bourne if possible) so as portable as possible.

有人有一些用于这项工作的工具吗?请让我知道吗?

Anyone have some tool they use for this job? please let me know?

感谢您的期待!?!:)

Thanks in anticipation!?! :)

我曾经尝试过的测试脚本:

Test script that I used to try this:

#!/bin/bash

# Core Functions and Alias definitions Used For Script Setup
getScriptFile () { basename "${BASH_SOURCE[0]}" ; } # This filename as executed
getScriptPath () { local f="${BASH_SOURCE[0]}" ; local p ; while test -L "${f}"
    do p="$( cd -P "$( dirname "${f}" )" && pwd )" ; f="$( readlink "${f}" )" &&
    f="${p}/${f}" ; done ; p="$( cd -P "$( dirname "${f}" )" && pwd )" ;
    printf "%s\n" "${p}" ; }
shopt -s expand_aliases
SCRIPTpath="$( getScriptPath ; )"
SCRIPTfile="$( getScriptFile ; )"
testEq () { [ "${1}" = "${2}" ] ; } # True if there is equality
testMatch () { [[ ${1} =~ ${2} ]] ; } # True if ARG1 MATCHES the Extended RegEx in ARG2
testInt () { [ "${1}" -eq "${1}" ] 2>/dev/null ; }  # I know this is okay with bash, to test with bourne
testIntLt () { testInt "${1}" && testInt "${2}" && [ "${1}" -lt "${2}" ] ; }
testSourced () { local c="0" ; local m="${#BASH_SOURCE[@]}" ; until testEq "$( basename "${BASH_SOURCE[${c}]}" )" "${SCRIPTfile}" && testMatch "${FUNCNAME[${c}]}" "source|main" && testIntLt "${c}" "${m}" ; do ((c++)) ; done ; if testEq "source" "${FUNCNAME[${c}]}" ; then return 0 ; else return 1 ; fi ; } # True if calling script is sourced into environment
getValidTerminationCommand () { testSourced && printf "%s" "return" || printf "%s" "exit" ; }
alias finish='eval $( getValidTerminationCommand ; )'

anotherTestFunc ()
{
    echo "anotherTestFunc ()"
    finish 10
    echo "anotherTestFunc () finish"
}

testFunc ()
{
    echo "testFunc ()"
    anotherTestFunc
    echo "testFunc () finish"
}

echo test start
testFunc
echo more outside text

推荐答案

不幸的是,没有一种普遍的观点认为仅从源脚本中退出.例如,采取以下情形:

Unfortunately there is no universal notion of exiting from just the script that was sourced; for example take the following scenario:

  • 脚本 a.sh 具有方法 a1() a2()
  • 脚本 b.sh 具有方法 b1() b2()
  • 现在 a1 正在调用 b1 ,后者本身正在调用 a2
  • script a.sh has methods a1() a2()
  • script b.sh has methods b1() b2()
  • now a1 is calling b1 which itself is calling a2

从a2内退出脚本a.sh是什么意思?只是离开 a2()?还是通过 b1() a1()展开调用堆栈?

From within a2, what does exiting from script a.sh mean? just leaving a2()? Or unwinding the call stack through b1() and a1() as well?

因此,即使有别名问题(*),也没有简单的命令可从方法内运行以仅从当前脚本退出. return 将仅从方法返回,而 exit 将杀死整个外壳.没什么.

Hence, even notwithstanding the alias issue (*), there is no simple command that you can run from within a method to exit just from the current script. return will return from only the method, and exit will kill the entire shell. Nothing in-between.

现在,bash通过FUNCNAME数组使调用堆栈可用,因此应该可以编写一个系统,在其中强制所有方法立即返回(使用DEBUG陷阱处理程序)直到调用中所需的位置堆.但是我不会将我的项目押在这样的功能上:)

Now, bash makes the call stack available through the FUNCNAME array, so it should be possible to write a system where you would force all methods to return immediately (using a DEBUG trap handler) up to the point you want in the call stack. But I wouldn't bet my projects on such a feature :)

也许更好的解决方案是通过在某个时候显式启动子外壳来引入一些退出边界".在上面的示例中,假设 a1 从子外壳中调用 b1 ,则 a2 中的退出将自动退回到a1 .但是,如果您需要在调用的方法中修改外壳状态(例如变量),这将无济于事.

Perhaps a better solution is to introduce some "exit boundaries" by explicitly starting sub-shells at some point. In my example above, assuming a1 calls b1 from within a sub-shell, then an exit in a2 will automatically fall back to a1. However this won't help if you need to modify the shell state (e.g. variables) from within the methods you call.

(*)恕我直言,您对别名限制的解决方法非常聪明

(*) IMHO your work-around for the alias limitation is quite smart by the way

这篇关于如何创建通用Shell命令以退出已执行或源脚本的脚本或从中返回脚本?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆