以相反顺序打印外壳参数


12

我有点卡住。我的任务是按相反的顺序将参数打印到脚本中,但第三个和第四个除外。

我的代码是这样的:

#!/bin/bash

i=$#
for arg in "$@"
do
    case $i
    in
        3) ;;
        4) ;;
        *) eval echo "$i. Parameter: \$$i";;
    esac
    i=`expr $i - 1`
done

由于我讨厌eval(向PHP致意),因此我在寻找一种没有解决方案的解决方案,但找不到解决方案。

如何动态定义参数的位置?

PS:不,这不是一项家庭作业,我正在学习考试外壳,因此我尝试解决旧的考试。

Answers:


13

eval是通过其动态选择的位置访问位置参数的唯一便携式方法。如果您显式循环索引而不是值(未使用的值),则脚本将更加清晰。请注意,expr除非您希望脚本在古董Bourne外壳中运行,否则不需要。$((…))算术在POSIX中。将的使用限制为eval尽可能小的片段;例如,不要使用eval echo,将值分配给一个临时变量。

i=$#
while [ "$i" -gt 0 ]; do
  if [ "$i" -ne 3 ] && [ "$i" -ne 2 ]; then
    eval "value=\${$i}"
    echo "Parameter $i is $value"
  fi
  i=$((i-1))
done

在bash中,您可以${!i}用来表示名称为的参数的值$i。当$i是命名参数或数字(表示位置参数)时,此方法有效。在使用它时,您可以使用其他bash便利功能。

for ((i=$#; i>0; i--)); do
  if ((i != 3 && i != 4)); then
    echo "Parameter $i is ${!i}"
  fi
done

我无法使用,arg因为它们的订购正确且没有相反的顺序。对于的使用expr,我仅限于使用标准。
WarrenFaith 2011年

1
@WarrenFaith如果脚本以开头#!/bin/bash,则可以使用${!i}(())。如果要坚持使用标准 sh,则没有可用,但是可用$((…))
吉尔(Gilles)'所以

好的,我想我可以解决这个问题:)
WarrenFaith 2011年

请注意,无论如何,古董Bourne弹壳都无法访问$ 9以上的位置参数。
斯特凡Chazelas

1
eval不是Ryan所展示的唯一可移植的方法。
斯特凡Chazelas

7

reverse在执行此操作的路径上保留了一个脚本:

#!/bin/sh

if [ "$#" -gt 0 ]; then
    arg=$1
    shift
    reverse "$@"
    printf '%s\n' "$arg"
fi

用法示例:

$ reverse a b c '*' '-n'
-n
*
c
b
a

您也可以使用函数代替专用脚本。


需要注意的是一个(与其他答案公布至今)也将在Bourne shell的(不是说有任何理由使用该shell现在)工作
斯特凡Chazelas

@StéphaneChazelas:为什么要引用$#?可以是非负整数吗?
G-Man说'Resstate Monica'15

2
@ G-Man,在列表上下文中不加引号的变量正在调用split + glob运算符。没有理由要在这里调用它。这与变量的内容无关。另请参见unix.stackexchange.com/a/171347。还要注意,某些外壳程序(例如破折号,豪华)仍从环境继承IFS。
斯特凡Chazelas

我发现,在Android上,我不得不声明local arg=$1
starfry


2

假设位置参数不包含换行符:

[ "$#" -gt 0 ] && printf '%s\n' "$@" | #print in order
sed '3,4 d' |                          #skip 3 and 4
tac                                    #reverse (try tail -r on
                                       #non-GNU systems).

测试:

set 1 2 3 4 5 6
printf '%s\n' "$@" | 
sed '3,4 d' |
tac

测试输出:

6
5
2
1

1

zsh

$ set a 'foo bar' c '' '*'
$ printf '<%s>\n' "${(Oa)@}"
<*>
<>
<c>
<foo bar>
<a>

Oa是参数扩展标志,用于在反向数组索引扩展后对数组元素进行排序。

排除3和4:

$ printf '<%s>\n' "${(Oa)@[5,-1]}" "${(Oa)@[1,2]}"
<*>
<foo bar>
<a>

0

基本上任何外壳:

printf '{ PS4=\${$(($#-$x))}; } 2>&3; 2>&1\n%.0s' |
x=LINENO+1 sh -sx "$@" 3>/dev/null

而且您不需要使用子外壳。例如:

set -x a b c
{ last= PS4=\${last:=\${$#}}; set +x; } 2>/dev/null
echo "$last"

...印刷品...

c

这是一个shell函数,可以alias为您设置一个shell ,该shell 将向前或向后打印参数:

tofro() case $1 in (*[!0-9]*|'') ! :;;(*) set "$1"
        until   [ "$1" -eq "$(($#-1))" ]    &&
                shift && alias args=":; printf \
            \"%.\$((\$??\${#*}:0))s%.\$((!\$??\${#*}:0))s\n\" $* "
        do      [ "$#" -gt 1 ] &&
                set "$@ \"\${$#}\" " '"${'"$((1+$1-$#))"'}"' ||
                set "$1" '"$1" "${'"$1"'}"'
        done;   esac

它不会尝试存储任何参数的文字值,而是将这样的字符串放入args alias

:;printf    "%.$(($??${#*}:0))s%.$((!$??${#*}:0))s\n" \
            "$1" "${3}" "${2}"  "${2}" "${3}"  "${1}"

...因此仅向后和向前存储对参数的引用。它将存储最多一个作为参数给定的计数。因此,上面的alias生成如下:

tofro 3

printf的行为会受到前一个命令的返回值的影响,该返回值始终:为null命令,因此通常为true。printf每次打印时将跳过其参数的一半-默认情况下,这会将参数从最小编号打印到最大编号。但是,如果您只是这样做:

! args

...将它们反向打印。

因为别名不存储任何文字值,所以当实际的args可能更改时,别名的值保持静态,但仍将引用尽可能多的别名。例如:

set one two three
tofro 3
args; ! args
shift; args; ! args

...打印...

one
two
three
three
two
one
two
three


three
two

但是重置别名可以像这样完成:

tofro 2
args; ! args

...这样就可以打印...

two
three
three
two

-3
declare -a argv=( "$@" )
for (( i=$((${#argv[@]}-1)) ; i>=0 ; i=$(($i-1)) )) ; do
        echo "${argv[$i]}"
        # use "${argv[$i]}" in here...
done

2
完全没有解释,很好。拒绝我。解释一下您的代码的作用,我可能会改变主意。
WarrenFaith 2015年

3
可以简化为for ((i = $# - 1; i >= 0; i--))
斯特凡Chazelas

我在幻觉吗?我以为我在问题中看到说“打印出论点……除了第三和第四”之外的单词。
G-Man说'恢复莫妮卡'

1
@ G-Man,标题说“反向打印参数”,这是在不使用eval的情况下回答的。从中排除3和4是微不足道的。我认为反对票是没有道理的。这也是不言自明的(尽管正如我所说可以简化很多)。
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.