是否可以在bash中自动加载lazier(类似ksh的函数)功能?

问题描述 投票:-1回答:2

我的项目(来自ksh的端口)使用一些目录作为可自动加载的功能。在这些目录中,每个文件名都作为在文件内声明的函数的名称,通过采购该文件来声明(实现)该函数。每个目录都可以视为一个“包”,可以通过函数来​​增强bash内置集。我大约有20个软件包,每个软件包的功能数量可能很大(某些软件包可以达到30个)。


bash文档包括自动加载的示例实现:

https://www.apt-browse.org/browse/ubuntu/trusty/main/all/bash-doc/4.3-6ubuntu1/file/usr/share/doc/bash/examples/functions/autoload.v2

但是,该实现要求在shell启动时知道(并枚举)潜在的可自动加载的函数集。

是否有没有这种限制的实现?

bash function autoload
2个回答
0
投票

好了,问了之后,我转向'给予':)到SO社区。我研究了我需要的这种自动加载功能,并提出了2种实现方式,在此提供了一种实现方式,因此有些可能会建议增强功能或指出错误。我将第二个发布在第二个发布上。

这两个实现将运行一些测试用例,因此在介绍这些实现之前,我先介绍一些常见的测试用例。我们有2个目录a1 /和a2 /,这些目录中的主机函数定义位于与该函数同名的文件中,每个目录都可以被认为是一个“程序包”目录,其中包含该程序包的函数,然后其中的函数以程序包名称命名(目录名称),出于测试目的几乎没有例外。

./a1/ac_f3::
function ac_f3
{ echo "In a1 ac_f3() : args=$@"
}

./a1/a1_f1::
function a1_f1
{ echo "In a1_f1() : args=$@"
}


./a1/a1_f2::
function a1_f22
{ echo "In a1_f2() : args=$@"
}


./a2/ac_f3::
function f3
{ echo "In a2 ac_f3() : args=$@"
}

./a2/a2_f1::
function a2_f1
{ echo "In a2_f1() : args=$@"
}

ac_f3是一个没有命名空间的函数,然后对于目录a1 / a2 /都是通用的,但是实现方式不同,这是为了演示$ FPATH优先级。

a1_f2是伪造的,它没有实现功能a1_f2(),因此我们必须优雅地失败。

a1_f1,a2_f1,只需实现a1_f1()a2_f2(),并且必须找到并执行它。

command_not_found_handle实现

感谢查尔斯带来了command_not_found_handle选项,因为自动加载功能肯定与未找到“命令”这一事实有关,然后我们尝试找到一个自动加载以加载和执行。

但是令人惊讶的是,bash shell具有有趣的“功能”,即某些未记录的行为。

Bash文档说。

   If the search is unsuccessful, the shell searches for a defined
   shell function named command_not_found_handle.  If that
   function exists, it is invoked with the original command and
   the original command's arguments as its arguments, and the
   function's exit status becomes the exit status of the shell.
   If that function is not defined, the shell prints an error
   message and returns an exit status of 127.

这具有误导性,因为在这里我们讨论command_not_found_handle()函数的调用,然后我们可以从“ shell上下文”推断出这种情况,并非如此。

在shell逻辑中,我们无法获取别名,然后无法获取函数,然后无法获取'shell的外部程序',并且该shell已经处于子shell创建模式,因此command_not_found_handle( )会被调用,但会在子shell中。不是外壳上下文。可能还可以,但这里的“有趣功能”是所创建的子流程不干净,其$$和$ PPID设置不正确,可能有一天会被修复。要展示此bash功能,我们可以执行

function command_not_found_handle
{ echo $$ ; sh -c 'echo $PPID'
}

PW$ # In a shell context invocation    
PW$  command_not_found_handle
2746
2746

PW$ # In a subshell invocation (via command not found)    
PW$ qqq
2746
3090

返回到我们的自动加载功能,这意味着我们想在shell实例中安装更多功能,而在子shell中无法执行任何操作,因此,基本上command_not_found_handle()几乎没有什么帮助,除了表明我们已进入其父级,也无济于事(然后找不到命令),我们将在实现中利用此功能。

# autoload
# This file must be sourced
# - From your rc files if you need autoloadable fuctions from your
#   interactive shell
# - From any script that need autoloadable functions.
#
# The FPATH must be set with a set of dirs/ where to look to find
# file name match the function name to source and execute.
#
# Note that if FPATH is exported, this is a way to export functions to
# script subshells

# Create a default command_not_found_handle if none exist
declare -F command_not_found_handle >/dev/null ||
function command_not_found_handle { ! echo bash: $1 command not found>&2; }

# Rename current command_not_found_handle
_cnf_body=$(declare -f command_not_found_handle | tail -n +2)
eval "function _cnf_prev $_cnf_body"

# Change USR1 to your liking
CNF_SIG=USR1

function autoload
{ declare f=$1 ; shift
  declare d s
  for d in $(IFS=:; echo $FPATH)
  do s=$d/$f
    [ -f $s -a -r $s ] &&
    { . $s
      declare -F $f >/dev/null ||
      { echo "$s exist but don't define $f" >&2 ; return 127
      }
      $f "$@" ; return
    }
  done
  _cnf_prev $f "$@"
}

trap 'autoload ${BASH_COMMAND[@]}' $CNF_SIG
function command_not_found_handle
{ kill -$CNF_SIG $$
}

[警告​​,如果您曾经使用此'autoload'文件准备进行bash修复,则可能有一天会反映真实的$$ PPID,在这种情况下,您需要使用$ PPID而不是$$

结果。

PW$ . /path/to/autoload
PW$ FPATH=a1:a2

PW$ a1_f1 11a 11b 11c
In a1_f1() : args=11a 11b 11c

PW$ a2_f1 21a 21b 21c
In a2_f1() : args=21a 21b 21c

PW$ a1_f2 12a 12b 12c
a1/a1_f2 exist but don't define a1_f2

PW$ ac_f3 c3a c3b c3c
In a1 ac_f3() : args=c3a c3b c3c

PW$ qqq  
Command 'qqq' not found, did you mean:

  command 'qrq' from snap qrq (0.3.1)
  command 'qrq' from deb qrq

See 'snap info <snapname>' for additional versions.

我们在这里得到的是正确的,找到,加载,执行了a1_f1()a2_f1()。

a1_f2()找不到,尽管具有可以承载它的文件。

qqq调用显示了处理程序的链接,正在执行功能自动加载的拳头,然后是ubuntu命令未找到的包(如果已安装,这意味着我们不会失去command_not_found_handle()的用户体验。

请注意,这里没有'管理员'功能,例如添加/删除/重新加载功能。

添加是在$ FPATH中存在的目录中设置文件的问题

删除是删除源文件并取消设置-f功能的问题

重新加载是编辑源文件并取消设置-f功能的问题。

重载功能在交互式shell的开发过程中可以非常简洁,但是所有这些操作都可以通过简单的方式完成unset -f funcname,因此基本上您可以编辑源文件。取消设置功能,然后调用它,即可获取最新的功能。脚本守护程序中可能会发生同样的情况,可以向守护程序实现信号,并且陷阱处理程序将简单地取消设置一组函数,然后在不停止/重新启动守护程序的情况下重新加载它们。

[这里的另一个功能是可以使用shell'package',即源文件可以实现'许多'功能,其中一些是外部API,其他是该包的内部功能,因为外壳中的所有内容都是扁平的,因此该函数具有名称空间,然后可以将每个外部API函数(尽管有文档说明)硬链接到同一文件。使用的第一个外部API将加载所有程序包功能。

在我的项目中,从软件包源中提取文档,然后此时推断并构建硬链接。

优点和缺点

专业人士在这里,我们在自动加载源中得到了一个简单的签名,即从脚本或bash rc文件(交互式)中,对autoload()的定义是适度的。

这是非常动态的,从某种意义上说,真正地推迟了函数的加载和执行,直到真正需要时为止。

CONs它获取一个信号号,如果command_not_found_handle()是从shell上下文调用的真实函数,则不需要此信号,这可能有一天发生。

它是基于bash功能实现的,该功能可能会移动(错误,$ PPID美元,然后需要维护移动目标。

结论这个实现对我来说还可以(我不在乎失去SIGUSR1)。理想的解决方案是将command_not_found_handle()清晰地实现,然后在shell上下文中调用它。没有任何信号,类似的实现也是可能的。


0
投票

这是第二种实现,以避免在以前的实现中看到信号使用情况以及似乎不太稳定的command_not_found_handle()的使用。

autoload::
function autoload
{ local d="$1" && [ "$1" ] && shift && autoload "$@"  
  local identifier='^[_a-zA-Z][_a-zA-Z0-9]*$'
  [ -d "$d"  -a -x "$d" ] && cd "$d" &&
  { for f in *
    do [[ $f =~ $identifier ]] && alias $f=". $PWD/$f;unalias $f;$f"
    done
    cd ->/dev/null 2>&1
  }
}


autoload $@  $(IFS=:; echo $FPATH)

这里我们还是以rc文件或脚本的形式来获取此autolaod文件。

实际上并不需要使用FPATH(有关FPATH的更多详细信息,请参阅注释)

因此,基本上,这个想法是将自动加载文件与要查找的目录一起提供。

PW$ . /path/to/autoload a1 a2
PW$ alias | grep 'a[12c]_*'
alias a1_f1='. /home/phi/a1/a1_f1;unalias a1_f1;a1_f1'
alias a1_f2='. /home/phi/a1/a1_f2;unalias a1_f2;a1_f2'
alias a2_f1='. /home/phi/a2/a2_f1;unalias a2_f1;a2_f1'
alias ac_f3='. /home/phi/a1/ac_f3;unalias ac_f3;ac_f3'

PW$ declare -F |  grep 'a[12c]_*'

自动加载源之后,我们定义了所有别名,但没有函数。

这比以前的实现要重一些,但是即使在外壳中创建别名也很轻巧,即使有数百个别名也不会很昂贵。

PW$ a1_f1 11a 11b 11c
In a1_f1() : args=11a 11b 11c

PW$ a2_f1 21a 21b 21c
In a2_f1() : args=21a 21b 21c

PW$ alias | grep 'a[12c]_*'
alias a1_f2='. /home/phi/a1/a1_f2;unalias a1_f2;a1_f2'
alias ac_f3='. /home/phi/a1/ac_f3;unalias ac_f3;ac_f3'
PW$ declare -F |  grep 'a[12c]_*'

declare -f a1_f1
declare -f a2_f1

这里我们看到a1_f1()和a2_f2()然后被加载并执行,它们从别名列表中删除并添加到函数列表中。

PW$ a1_f2 12a 12b 12c
a1_f2: command not found

PW$ ac_f3 c3a c3b c3c
In a1 ac_f3() : args=c3a c3b c3c

PW$ qqq 

Command 'qqq' not found, did you mean:

  command 'qrq' from snap qrq (0.3.1)
  command 'qrq' from deb qrq

See 'snap info <snapname>' for additional versions.

在这里,我们发现未找到a1_f2(),并且与以前的实现中的报告不一样。

ac_f3()是预期的a1 /中的一个。

qqq如果已安装,仍会提供未找到命令的发行版软件包结果(正常情况下,我们不会与command_not_found_handle()混淆)

优点和缺点

专业人士不坐在bash的bug上,即bash更新后可以存活一段时间。

CONs比以前的实现重一些,但可以接受。

更简单,可能不会更简单,但肯定比bash文档中建议的示例要短,并且更懒惰,即仅在必要时才加载函数(虽然不是别名)

多功能“程序包”文件以及用于外部API公开的硬链接的性能较差,因为每个外部API函数(硬链接)都会触发文件的重新加载,除非正确编写了程序包文件,并在加载后删除了所有多余的别名。

© www.soinside.com 2019 - 2024. All rights reserved.