从右侧移动 bash 参数

从右侧移动 bash 参数

我有一个脚本,用户将在其前面添加参数而不是附加参数,即他们可能会调用command Ccommand B Ccommand A B C等。

我希望能够简单地从右侧移动这些参数,就像您可以使用shift.

我正在想象一个shift-right行为如下的命令:

echo "$@"    # A B C
shift-right
echo "$@"    # A B
shift-right
echo "$@"    # A
shift-right
echo "$@"    # 
echo "$#"    # 0

有没有一种干净的方法来实现这一点?我知道我可以解决这个问题,但是shift类似的解决方案会更好、更简单。


为了响应 XY 问题评论,我的具体用例是一个采用端口或主机和端口的命令,例如command 123command remotehost 123。我不希望用户必须以相反的顺序指定这些(端口然后主机)。

说这样的话会相当干净(显然未经测试):

port=${@: -1}
shift-right
host=${1:-localhost}

但实际上,我对这个问题很好奇,即使有更好的方法来解决这个特定的例子。

这是一种相当干净的方法来处理没有 的两个参数的情况shift-right,仅供参考:

port=${@: -1}
host=${2:+$1}
host=${host:-localhost}

但希望您能够理解随着参数数量的增加,它是如何变得更加笨拙的。

答案1

如果位置参数列表是:

$ set -- 0wer 1wdfg 2erty 333 4ffff 5s5s5

然后这将打印没有最后一个参数的参数:

$ echo "${@:1:$#-1}"
0wer 1wdfg 2erty 333 4ffff

当然,参数也可以这样设置:

$ set -- "${@:1:$#-1}"
$ echo $@
0wer 1wdfg 2erty 333 4ffff

这适用于 bash 2.0 或更高版本。

对于其他更简单的 shell,您需要一个(有点棘手的)循环来删除最后一个参数:

unset b; for a; do set -- "$@" ${b+"$b"}; shift; b="$a"; done

答案2

您可以简单地检查参数的数量并以相反的顺序处理它们,例如对于简单的两个参数的情况,其中第一的参数是可选的:

usage() {
  cat << EOF
Usage: $0 [host] port
EOF
}

[ "$#" -eq 0 ] && { usage; exit 1;}

if [ "$#" -gt 1 ]; then
  myhost="$1"
  shift
fi

myport="$1"

不过,您也提到了三个参数的想法。拥有三个由位置确定的可选参数并不是一件小事 - 如果您想指定第一个和第三个而不是第二个怎么办?这种情况几乎不可能单独使用位置参数来处理,但如果正确使用getopts.

usage() {
  cat << EOF
usage: $0 [OPTIONS]

    -p    port number (required)
    -h    hostname (optional)
EOF
}

while getopts :p:h: opt; do
  case "$opt" in
    p)
      myport="$OPTARG"
      ;;
    h)
      myhost="$OPTARG"
      ;;
    :)
      printf %s\\n "Argument required for option -$OPTARG" >&2
      usage >&2
      exit 1
      ;;
    \?)
      printf %s\\n "Unknown option -$OPTARG" >&2
      usage >&2
      exit 1
      ;;
  esac
done
shift "$((OPTIND-1))"

if [ -z "$myport" ]; then
  printf %s\\n "You must supply a port" >&2
  usage >&2
  exit 1
fi

答案3

回到我自己的问题,今天我可能会简单地使用一个单独的数组来实现它并检查传递的参数的数量,例如:

# Just a demo of a command that "shifts right"
# Usage: url_builder [[[scheme] hostname] port] 
url_builder() {
  local parts=(http localhost 80) # defaults
  if (( $# == 3 )); then
    parts[0]=$1; shift
  fi
  if (( $# == 2 )); then
    parts[1]=$1; shift
  fi
  if (( $# == 1 )); then
    parts[2]=$1; shift
  fi
  printf '%s://%s:%s\n' "${parts[@]}"
}

用法:

$ url_builder
http://localhost:80

$ url_builder 100
http://localhost:100

$ url_builder example.com 100
http://example.com:100

$ url_builder ftp example.com 100
ftp://example.com:100

这是否比 user79743 建议的方法更好set可能取决于用例,但对于超过几个参数的任何事情,我首先都会开始对该模式表示怀疑。用户可能会对带有大量位置参数的命令感到有些惊讶,这些位置参数在左侧是可选的,如下所示。

相关内容