无法在函数中执行 awk 命令,但直接在 shell 中工作

Gur*_*ngh 1 bash awk

我想为 bash 创建一个实用函数来删除重复的行。我正在使用功能

function remove_empty_lines() {
  if ! command -v awk &> /dev/null
  then
    echo '[x] ERR: "awk" command not found'
    return
  fi

  if [[ -z "$1" ]]
  then
    echo "usage: remove_empty_lines <file-name> [--replace]"
    echo
    echo "Arguments:"
    echo -e "\t--replace\t (Optional) If not passed, the result will be redirected to stdout"
    return
  fi

  if [[ ! -f "$1" ]]
  then
    echo "[x] ERR: \"$1\" file not found"
    return
  fi
  echo $0
  local CMD="awk '!seen[$0]++' $1"

  if [[ "$2" = '--reload' ]]
  then
    CMD+=" > $1"
  fi

  echo $CMD
}
Run Code Online (Sandbox Code Playgroud)

如果我直接运行主 awk 命令,它就可以工作。但是当我在函数中执行相同的 $CMD 时,我收到此错误

$ remove_empty_lines app.js
/bin/bash
awk '!x[/bin/bash]++' app.js
Run Code Online (Sandbox Code Playgroud)

Cha*_*ffy 6

原始代码在几个方面被破坏:

  • 当与 一起使用时--reload,它会在awk读取这些内容之前截断输出文件的内容(请参阅如何在命令中使用文件并将输出重定向到同一个文件而不截断它?
  • 实际上从未运行过命令,并且由于BashFAQ #50 中描述的原因,将 shell 命令存储在字符串中本质上是有问题的(可以使用eval; BashFAQ #48来解决其中的一些问题;BashFAQ #48描述了为什么这样做会引入安全性错误)。
  • 它将错误消息(和其他“诊断内容”)写入 stdout 而不是 stderr;这意味着如果您的函数的输出被重定向到一个文件,您将永远看不到它的错误——它们最终会混入输出中。
  • return$?为零的情况下,错误情况用偶数处理;这意味着它return本身将返回零/成功/真实状态,不会向调用者透露发生了任何错误。

据推测,您将输出存储在其中的原因CMD是能够有条件地执行重定向,但这可以通过其他方式完成:下面,我们始终创建一个文件描述符out_fd,但将其指向stdout(在没有调用时--reload)或指向临时文件(如果用 调用--reload);if-and-only-if awk 成功,然后我们将临时文件移动到输出文件上,从而将其替换为原子操作。

remove_empty_lines() {
  local out_fd rc=0 tempfile=
  command -v awk &>/dev/null || { echo '[x] ERR: "awk" command not found' >&2; return 1; }

  if [[ -z "$1" ]]; then
    printf '%b\n' >&2 \
      'usage: remove_empty_lines <file-name> [--replace]' \
      '' \
      'Arguments:' \
      '\t--replace\t(Optional) If not passed, the result will be redirected to stdout'
    return 1
  fi

  [[ -f "$1" ]] || { echo "[x] ERR: \"$1\" file not found" >&2; return 1; }

  if [ "$2" = --reload ]; then
    tempfile=$(mktemp -t "$1.XXXXXX") || return
    exec {out_fd}>"$tempfile" || { rc=$?; rm -f "$tempfile"; return "$rc"; }
  else
    exec {out_fd}>&1
  fi

  awk '!seen[$0]++' <"$1" >&$out_fd || { rc=$?; rm -f "$tempfile"; return "$rc"; }
  exec {out_fd}>&- # close our file descriptor

  if [[ $tempfile ]]; then
    mv -- "$tempfile" "$1" || return
  fi
}
Run Code Online (Sandbox Code Playgroud)