シェル引数を逆順で出力します


12

私は少し立ち往生しています。私の仕事は、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:いいえ、宿題ではありません。試験のシェルを学んでいるので、古い試験を解決しようとしています。

回答:


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、標準のみを使用するように制限されています。
ウォーレンフェイス

1
@WarrenFaithスクリプトが始まる場合#!/bin/bashは、使用することができます${!i}(())標準の sh に固執したい場合、これらは利用できませんが、利用可能$((…))です。
ジル 'SO-悪であるのをやめる'


いずれにしても、アンティークのBourneシェルは9ドルを超える位置パラメーターにアクセスできないことに注意してください。
ステファンシャゼル

1
evalライアンが示したような唯一のポータブルな方法ではない
ステファンシャゼラス

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

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


その1つの(他の回答とは反対に、これまで掲載)ことを注意いただいてもBourneシェル(ない頃はシェルの使用に何らかの理由があること)での作業
ステファンChazelas

@StéphaneChazelas:なぜ引用するの$#ですか?非負の整数以外のものを使用できますか?
Gマンは「Reinstate Monica」と言います

2
@ G-Man、リストコンテキストで変数を引用符で囲まないでおくと、split + glob演算子が呼び出されます。ここで呼び出したい理由はありません。これは変数の内容とは関係ありません。最後にunix.stackexchange.com/a/171347も参照してください。また、一部のシェル(たとえば、ダッシュ、上流)は環境からIFSを継承していることに注意してください。
ステファンシャゼル

Androidでは、宣言する必要があることがわかりました。local arg=$1
スターフライ

2

これは、evalの正しい非危険な使用です。作成するコンテンツを完全に制御しますeval

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


その${!i}標準構文の一部ではないですか?
ウォーレンフェイス

いいえ、それはバシズムです。POSIXパラメーターの展開を次に示し
ショーンJ.ゴフ

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

そして、ここにalias引数を前方または後方に出力するシェルを設定できるシェル関数があります:

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

...逆に印刷します。

エイリアスはリテラル値を保存しないため、その値は静的なままですが、実際の引数は変更される可能性がありますが、それでも可能な限り多くを参照します。例えば:

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
まったく説明がありません、いいですね。私からの投票。あなたのコードが何をするのか説明してください。そうすれば気が変わるかもしれません。
ウォーレンフェイス

3
簡略化できますfor ((i = $# - 1; i >= 0; i--))
ステファンシャゼラス

私は幻覚を感じていますか?「議論を印刷してください... 3番目と4番目を除いて」という質問に言葉を見たと思いました。
G-Manは「Reinstate Monica」と言います

1
@ G-Man、タイトルには「引数を逆に印刷する」と書かれていますが、これはevalを使用せずに答えています。それから3と4を除外するのは簡単です。ダウン票が正当化されるとは思わない。それはまた自明です(私が言ったように、かなり簡単にできるかもしれませんが)。
ステファンシャゼル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.