简体   繁体   English

如何创建通用的shell命令以从执行或源脚本中退出或返回?

[英]How can I create a generic shell command to exit or return from a script that is either executed or sourced?

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

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. 同样,如果我使用return,则会遇到以下问题:a)它只会返回到调用函数,而不会在没有其他逻辑的情况下结束整个脚本的运行。 b) If I use return in the main script and execute it, it will error rather than terminating with a level. b)如果我在主脚本中使用return并执行它,它将出错而不是终止某个级别。

The best solution I have come up with is this: 我想出的最好的解决方案是:

All shell scripts I write start with this preamble anyway to get some stable base variables: 无论如何,我编写的所有shell脚本都从此序言开始,以获得一些稳定的基本变量:

# 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 ; )'

...and when I want to finish a script abruptly, I use my new finish command, ie ...当我想突然完成脚本时,我使用了新的finish命令,即

$ finish 5  <-- to finish with an error level of 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" . 现在,无论脚本是源代码还是执行的脚本,我都将获得5的退出代码。对于错误类型的“ exit”或“ return”,没有错误。

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. 我在getScriptPath命令上遇到麻烦的原因是要处理可能位于符号链接下的文件系统路径上的脚本文件。

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, ie it needs to make sure that it is testing the fact that that itself is sourced, not say a calling script. 之所以在testSourced的复杂性在于,测试需要工作,即使如果来源是由调用脚本这样做,即它需要确保它正在测试一个事实,即本身是测试脚本来源,而不是调用脚本。

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? 我是否缺少明显的东西?

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). 只是要确认一下,我想做的是:-单个命令立即终止脚本-如果执行了脚本,则执行相同的命令,源代码-如果在函数,主脚本主体或子函数中使用,则执行相同的命令-执行如果该脚本是直接调用的或由另一个可能执行或源自的脚本调用或提供的,则也是相同的(或可能导致执行该脚本的n个执行级别)。 - use as standard commands to the Bash shell (Bourne if possible) so as portable as possible. -作为对Bash Shell(如果可能的话,Bourne)的标准命令使用,以便于移植。

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: 例如,采取以下情况:

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

From within a2, what does exiting from script a.sh mean? 从a2内退出脚本a.sh是什么意思? just leaving a2() ? 刚离开a2()吗? Or unwinding the call stack through b1() and a1() as well? 还是通过b1()a1()展开调用堆栈?

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. return将仅从方法返回,而exit将杀死整个外壳。 Nothing in-between. 两者之间没有。

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. 现在,bash通过FUNCNAME数组使调用栈可用,因此应该可以编写一个系统,在其中强制所有方法立即返回(使用DEBUG陷阱处理程序)直到调用栈中所需的位置。 But I wouldn't bet my projects on such a feature :) 但是我不会将我的项目押在这样的功能上:)

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 . 在上面的示例中,假设a1从子外壳中调用b1 ,则a2的退出将自动退回到a1 However this won't help if you need to modify the shell state (eg variables) from within the methods you call. 但是,如果您需要在调用的方法中修改外壳状态(例如,变量),这将无济于事。


(*) IMHO your work-around for the alias limitation is quite smart by the way (*)恕我直言,您对别名限制的解决方法非常聪明

combining bash-exit-script-from-inside-a-function with your test example, this returns 10 for both cases: bash-exit-script-from-inside-a-function与您的测试示例结合在一起,在两种情况下均返回10:

#!/bin/bash

returnBreak () { SCRIPTresult=${1:-0} ; break 10000 ; }
setExitCode () { local s=$? ; [[ -z $SCRIPTresult ]] && return $s ; return $SCRIPTresult ; }

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

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

SCRIPTresult=""
for null in null;
do
    echo test start
    testFunc
    echo more outside text

done
setExitCode

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM