web-dev-qa-db-ja.com

シェル引数を逆の順序で出力する

少し行き詰まっています。私の仕事は、スクリプトの引数を3番目と4番目を除いて逆の順序で出力することです。

私が持っているのはこのコードです:

#!/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:いいえ、宿題ではありません。私は試験のためにシェルを学んでいるので、古い試験を解こうとしています。

12
WarrenFaith

evalは、動的に選択された位置によって位置パラメータにアクセスする唯一の移植可能な方法です。値ではなく(使用していない)インデックスを明示的にループすると、スクリプトがより明確になります。アンティークのBourneシェルでスクリプトを実行したい場合を除き、exprは必要ありません。 $((…))演算は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

私は自分のパスにこれを実行するスクリプト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

専用スクリプトの代わりに関数を使用することもできます。

7
Ryan Williams

定位置パラメーターに改行文字が含まれていないと想定します。

[ "$#" -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
2
PSkocik

これは、evalの正しい使用であり、危険ではありません。 evalingするコンテンツを完全に制御します。

それでも悪い感じがする場合は、移植性を気にしないのであれば、Bashの${!i}間接構文を使用できます。

2
Shawn J. Goff

zshの場合:

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

Oaは、拡張時に配列要素をreverse配列インデックスでソートするためのパラメーター拡張フラグです。

3と4を除外するには:

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

基本的にどんなシェルでも:

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

そして、サブシェルを使用するためにneedを使用しないでください。例えば:

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

...プリント...

c

そして、ここにShell aliasを設定できる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

引数のリテラル値を格納するのではなく、argsaliasに次のような文字列を入れます。

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

...したがって、パラメータへの参照のみを逆方向および順方向に格納します。引数として与えられたカウントまで保存します。そして上記のaliasは次のように生成されました:

tofro 3

printfの動作は、前のコマンドの戻り値に基づいて影響を受けます。これは常に: nullコマンドなので、通常はtrueです。 printfは、出力するたびに引数の半分をスキップします。デフォルトでは、引数は番号の小さいものから大きいものの順に出力されます。ただし、次のようにした場合:

! 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
0
mikeserv