"alias method chain" 在 Bash 或 Zsh

"alias method chain" in Bash or Zsh

这是(或者 ,至少)Ruby 中的常见模式,但我不知道如何在 Zsh 或 Bash.

假设我有一个名为 "whoosiwhatsit" 的 shell 函数,我想在特定项目中覆盖它,同时仍以不同的名称保留原始函数。

如果我不太了解,我可能会尝试创建一个别名以指向 whoosiwhatsit,然后创建一个使用该别名的新 "whoosiwhatsit" 函数。当然可以,因为别名将引用新函数。

有什么方法可以实现我所说的吗?

在bash中,有一个名为BASH_ALIASES的内置变量,它是一个包含当前别名的关联数组。当你更新它时语义有点不一致 (RTM!) 但如果你限制自己阅读 BASH_ALIASES,你应该能够自己编写一个 shell 实现别名链接的函数。

通过可选地调用其覆盖的内置函数或命令的函数创建单层覆盖是很常见的,并且得到了很好的支持:

# Make all cd commands auto-exit on failure
cd() { builtin cd "$@" || exit; }

# Make all ssh commands verbose
ssh() { command ssh -vv "$@"; }

它不会超出 link,但它完全是 POSIX,并且在实践中通常比尝试在 Bash 中编写 Ruby 效果更好。

别名非常弱。你可以用函数来做到这一点。考虑以下工具:

#!/usr/bin/env bash

PS4=':${#FUNCNAME[@]}:${BASH_SOURCE}:$LINENO+'

rename_function() {
  local orig_definition new_definition new_name retval
  retval=; shift
  orig_definition=$(declare -f "") || return 1
  new_name="_"
  while declare -f "$new_name" >/dev/null 2>&1; do
    new_name+="_"
  done
  new_definition=${orig_definition/""/"$new_name"}
  eval "$new_definition" || return
  unset -f "$orig_definition"
  printf -v "$retval" %s "$new_name"
}

# usage: shadow_function target_name shadowing_func [...]
# ...replaces target_name with a function which will call:
# shadowing_func target_renamed_to_this number_of_args_in_[...] [...] "$@"
shadow_function() {
  local shadowed_func eval_code shadowed_name shadowing_func shadowed_func_renamed
  shadowed_name=; shift
  shadowing_func=; shift
  rename_function shadowed_func_renamed "$shadowed_name" || return
  if (( $# )); then printf -v const_args '%q ' "$@"; else const_args=''; fi
  printf -v eval_code '%q() { %q %q %s "$@"; }' \
    "$shadowed_name" "$shadowing_func" "$shadowed_func_renamed" "$# $const_args" 
  eval "$eval_code"
}

...以及这些工具的以下示例应用程序:

whoosiwhatsit() { echo "This is the original implementation"; }

override_in_directory() {
  local shadowed_func=; shift
  local override_cmd_len=; shift
  local override_dir=; shift
  local -a override_cmd=( )
  local i
  for (( i=1; i<override_cmd_len; i++)); do : ""
    override_cmd+=( "" ); shift
  done
  : PWD="$PWD" override_dir="$override_dir" shadowed_func="$shadowed_func"
  : override_args "${override_args[@]}"
  if [[ $PWD = $override_dir || $PWD = $override_dir/* ]]; then
      [[ $- = *x* ]] && declare -f shadowed_func >&2 # if in debugging mode
      "${override_cmd[@]}"
  else
      "$shadowed_func" "$@"
  fi
}

ask_the_user_first() {
  local shadowed_func=; shift;
  shift # ignore static-argument-count parameter
  if [[ -t 0 ]]; then
    read -r -p "Press ctrl+c if you are unsure, or enter if you are"
  fi
  "$shadowed_func" "$@"
}

shadow_function whoosiwhatsit ask_the_user_first

shadow_function whoosiwhatsit \
  override_in_directory /tmp echo "Not in the /tmp!!!"

shadow_function whoosiwhatsit \
  override_in_directory /home echo "Don't try this at home"

最终结果是一个 whoosiwhatsit 函数,当它的标准输入是 TTY 时,它会在执行任何操作之前询问用户,并且当 运行 在 /tmp/home.


也就是说,我不容忍这种做法。将上述内容视为一项智力练习。 :)