/ bin / sh関数の最後の引数を取得する方法


11

実装するより良い方法は何print_last_argですか?

#!/bin/sh

print_last_arg () {
    eval "echo \${$#}"  # this hurts
}

print_last_arg foo bar baz
# baz

(もしこれが、私が何をすべきかを知る#!/usr/bin/zsh代わりに、と言う#!/bin/shなら。私の問題は、これをに実装するための合理的な方法を見つけることです#!/bin/sh。)

編集:上記はばかげた例にすぎません。私の目標はしないが、印刷最後の引数をではなく、シェル関数内で最後の引数を参照する方法を持っています。


EDIT2:私はそのような不明確な言葉の質問をお詫び申し上げます。 今回はうまくいきたいです。

これがの/bin/zsh代わりだったら/bin/sh、私はこのようなものを書くことができます

#!/bin/zsh

print_last_arg () {
    local last_arg=$argv[$#]
    echo $last_arg
}

$argv[$#]は、シェル関数内の最後の引数を参照する方法として最初のEDITで説明したものの例です。

したがって、私は本当に次のように私の元の例を書くべきでした:

print_last_arg () {
    local last_arg=$(eval "echo \${$#}")   # but this hurts even more
    echo $last_arg
}

...私が求めているのは、割り当ての右側に置くのはそれほどひどくないことであることを明確にするためです。

ただし、すべての例で、最後の引数は非破壊的にアクセスされることに注意してください。IOW、最後の引数にアクセスしても、全体として位置引数は影響を受けません。


unix.stackexchange.com/q/145522/117549は、#!/ bin / shのさまざまな可能性を指摘しています-絞り込めますか?
ジェフシャラー

私は編集が好きですが、おそらく、最後の引数を参照する非破壊的な方法を提供する答えがここにあることに気付いたかもしれません...?あなたは同等var=$( eval echo \${$#})にすべきではありません-2 eval var=\${$#}つは似ていません。
mikeserv 2016年

1
最後のメモを取得したかわかりませんが、これまでに提供されたほとんどすべての回答は、実行中のスクリプト引数を保持するという意味で非破壊的です。無害な関数で使用しない限りshiftset -- ...ベースのソリューションのみが破壊的である可能性があります。
jlliagre 2016年

@jlliagre-しかし、それらは依然としてメインで破壊的です-ディスカバーして発見できるように、使い捨てコンテキストを作成する必要があります。しかし...とにかく2番目のコンテキストを取得した場合-インデックスを取得できるコンテキストを取得しないのはなぜですか?仕事用のツールを使用して何か問題がありますか?シェル拡張を拡張可能な入力として解釈することはevalの仕事です。また、安全な値が保証さeval "var=\${$#}"var=${arr[evaled index]}ていることを除いて$#、実行することに関して大きな違いはありません。セット全体をコピーして、直接インデックスを作成できるのにセットを破棄するのはなぜですか?
mikeserv 2016年

1
@mikeservシェルの主要部分で行われたforループは、すべての引数を変更せずに残しています。すべての引数のループは非常に最適化されていないことに同意します。特に、それらの数千がシェルに渡される場合、適切なインデックスを使用して最後の引数に直接アクセスすることが最善の答えであることにも同意します(そして、なぜそれが否定されたのか理解していません)しかし、それを超えて、実際に破壊的なものはなく、余分なコンテキストは作成されません。
jlliagre 2016年

回答:


1
eval printf %s${1+"'\n' \"the last arg is \${$#"\}\"}

...文字列のthe last arg is後に<space>、最後の引数の値、および少なくとも1つの引数がある場合は末尾の<newline>が続くか、引数がゼロの場合は何も出力しません。

あなたがした場合:

eval ${1+"lastarg=\${$#"\}}

...その後$lastarg、少なくとも1つの引数がある場合は、最後の引数の値をシェル変数に割り当てるか、まったく何もしません。どちらにせよ、あなたはそれを安全に行うでしょう、そしてそれはあなたがたオールド・ボーンの殻にさえ移植可能であるべきだと私は思います。

arg配列全体を2回コピーする必要があります(Bourneシェルにはprintfin$PATHが必要です)が、同様に機能する別の例を次に示します

if   [ "${1+:}" ]
then set "$#" "$@" "$@"
     shift    "$1"
     printf %s\\n "$1"
     shift
fi

コメントは詳細な議論のためのものではありません。この会話はチャットに移動しました
terdon

2
参考までに、引数が存在しない場合、レガシーボーンシェルでは最初の提案が「悪い置換」エラーで失敗します。残りの両方は期待どおりに動作します。
jlliagre 2016年

@jillagre-ありがとう。その1つについてはよくわかりませんでしたが、他の2つについてはかなり確信がありました。これは、参照によってインラインで引数にアクセスする方法を示すためのものでした。関数は${1+":"} returnすぐに何かを開くことができます-何も呼び出されなかった場合、誰が何かをしたり、あらゆる種類の副作用を危険にさらしたいのですか?数学も同じですeval。正の整数を拡張できることが確実であれば、1日中できます。$OPTINDそれは素晴らしいです。
mikeserv 2016年

3

これは単純な方法です:

print_last_arg () {
  if [ "$#" -gt 0 ]
  then
    s=$(( $# - 1 ))
  else
    s=0
  fi
  shift "$s"
  echo "$1"
}

(引数を渡さなかったときに元のものが失敗したという@cuonglmのポイントに基づいて更新されました。これにより、空白行がエコーされます。else必要に応じて、句の動作を変更してください)


これにはSolarisで/ usr / xpg4 / bin / shを使用する必要がありました。Solaris#!/ bin / shが必要かどうか教えてください。
ジェフシャラー

この質問は、私が書こうとしている特定のスクリプトではなく、一般的なハウツーについてです。このための良いレシピを探しています。もちろん、よりポータブルであるほど良いです。
kjo

$(())数学はSolaris / bin / shで失敗します。必要に応じて、 `expr $#-1`をシフトします。
ジェフシャラー

または、Solaris / bin / shの場合echo "$# - 1" | bc
Jeff Schaller

1
それはまさに私が書きたかったものですが、私が試した2番目のシェルで失敗しました-NetBSDは、それをサポートしていないAlmquistシェルのようです:(
Jeff Schaller

3

開始ポストの例を考えると(スペースのない位置引数):

print_last_arg foo bar baz

デフォルトの場合はIFS=' \t\n'、次のようにします。

args="$*" && printf '%s\n' "${args##* }"

より安全な展開のために、"$*"IFSを設定します(@StéphaneChazelasごとに):

( IFS=' ' args="$*" && printf '%s\n' "${args##* }" )

ただし、位置引数にスペースを含めることができる場合、上記は失敗します。その場合は、代わりにこれを使用してください。

for a in "$@"; do : ; done && printf '%s\n' "$a"

これらの手法は、の使用を避け、eval副作用がないことに注意してください。

shellcheck.netでテスト済み


1
最後の引数にスペースが含まれている場合、最初の引数は失敗します。
cuonglm

1
最初のものはPOSIX以前のシェルでも機能しなかったことに注意してください
cuonglm

@cuonglmよくわかりました。あなたの正しい観察結果が組み込まれました。
AsymLabs 2016年

また、の最初の文字$IFSはスペースであると想定しています。
ステファンChazelas

1
環境に$ IFSがない場合、それ以外の場合は指定されません。ただし、split + glob演算子を使用するたびに、仮想的にIFSを設定する必要があるため(展開は引用符で囲まないでください)、IFSを扱う1つの方法は、必要なときにいつでも設定できるようにすることです。IFS=' 'の拡張に使用されていることを明確にするためだけに、ここにあることは害になりません"$*"
ステファンChazelas

3

この質問は2年以上前のものですが、もう少しコンパクトなオプションを共有したいと思いました。

print_last_arg () {
    echo "${@:${#@}:${#@}}"
}

実行してみましょう

print_last_arg foo bar baz
baz

バッシュシェルパラメータ展開

編集

さらに簡潔: echo "${@: -1}"

(スペースを気にする)

ソース

macOS 10.12.6でテスト済みですが、ほとんどの利用可能な* nixフレーバーの最後の引数も返します...

害が少ない ¯\_(ツ)_/¯


これは受け入れられる答えになるはずです。さらに良いでしょう:echo "${*: -1}"これshellcheckは文句を言わないでしょう。
トム・ヘイル

4
これは、プレーンなPOSIX shでは機能しません${array:n:m:}。拡張機能です。(質問は明確に言及していました/bin/sh
ilkkachu

2

POSIXly:

while [ "$#" -gt 1 ]; do
  shift
done

printf '%s\n' "$1"

(このアプローチは古いBourneシェルでも機能します)

他の標準ツールで:

awk 'BEGIN{print ARGV[ARGC-1]}' "$@"

(これはawk、を持っていなかった古いで動作しませんARGV


ボーンシェルがまだある場合awkは、を持っていなかった古いawkである可能性もありますARGV
ステファンChazelas

@StéphaneChazelas:そうですね。少なくとも、ブライアンカーニハン自身のバージョンでは動作しました。理想はありますか?
cuonglm

これは引数を消去します。それらを維持する方法?

@BinaryZebra:awkアプローチを使用するか、他のforループのように他の単純なループを使用するか、関数に渡します。
cuonglm

2

これは、POSIX準拠のシェルで動作し、POSIX以前のレガシーSolaris Bourneシェルでも動作します。

do=;for do do :;done;printf "%s\n" "$do"

そして、これは同じアプローチに基づく関数です:

print_last_arg()
  if [ "$*" ]; then
    for do do :;done
    printf "%s\n" "$do"
  else
    echo
  fi

PS:関数本体を囲む中括弧を忘れたと言わないでください;-)


2
関数本体を囲む中括弧を忘れました;-)。

@BinaryZebra私はあなたに警告しました;-)私はそれらを忘れていませんでした。中かっこは驚くほどオプションです。
jlliagre 2016年

1
@jlliagre私は確かに警告されました;-):P ......そして:確かに!

構文仕様のどの部分でこれが可能ですか?
トム・ヘイル

@TomHaleシェルの文法規則in ...では、forループ内での欠落と、if関数本体として使用されるステートメントを中括弧で囲まないことの両方を許可しています。for_clauseおよびpubs.opengroup.org/onlinepubs/9699919799を参照しcompound_commandください
jlliagre 2018年

2

「UNIX -よくある質問」

(1)

unset last
if    [ $# -gt 0 ]
then  eval last=\${$#}
fi
echo  "$last"

引数の数がゼロになる可能性がある場合は、引数ゼロ$0(通常はスクリプトの名前)がに割り当てられ$lastます。それがifの理由です。

(2)

unset last
for   last
do    :
done
echo  "$last"

(3)

for     i
do
        third_last=$second_last
        second_last=$last
        last=$i
done
echo    "$last"

引数がないときに空行が出力されないようにするには、echo "$last"forを次のように置き換えます。

${last+false} || echo "${last}"

ゼロの引数カウントはによって回避されif [ $# -gt 0 ]ます。

これはページにリンクされている内容の正確なコピーではなく、いくつかの改善が追加されました。


0

これはperl(ほとんどのUNICESが)インストールされているすべてのシステムで機能するはずです。

print_last_arg () {
    printf '%s\0' "$@" | perl -0ne 's/\0//;$l=$_;}{print "$l\n"'
}

トリックは、各シェル引数の後に「」printfを追加するために使用し\0、次にそのスイッチをNULLに設定するスイッチです。次に、入力を繰り返し、を削除し、NULLで終了する各行をとして保存します。すべての入力が読み込まれた後にブロック(つまり、ブロック)が実行されるため、最後の「行」、つまり最後のシェル引数が出力されます。perl-0\0$lEND}{


@mikeservああ、本当です。最後の引数以外は改行でテストしていませんでした。編集されたバージョンはほとんど何でも機能するはずですが、おそらくそのような単純なタスクには複雑すぎます。
terdon

ええ、外部の実行可能ファイル(それがまだロジックの一部ではない場合)を呼び出すことは、私にとっては少し強引です。ポイントは上へその引数を渡すことである場合はsedawkまたはperlそれはとにかく貴重な情報である可能性があります。それでも、sed -z最新のGNUバージョンの場合と同じことができます。
mikeserv 2016年

なぜ反対票を投じたのですか?これは良かった…と思った?
mikeserv 2016年

0

これは再帰を使用したバージョンです。しかし、これがPOSIX準拠であるかどうかはわかりません...

print_last_arg()
{
    if [ $# -gt 1 ] ; then
        shift
        echo $( print_last_arg "$@" )
    else
        echo "$1"
    fi
}

0

単純な概念、演算なし、ループなし、evalなし、関数のみ。
Bourneシェルには演算がなかったことを覚えておいてください(externalが必要ですexpr)。算術の自由、eval自由な選択を得たい場合、これはオプションです。関数が必要なということは、SVR3以上(パラメーターの上書きなし)を意味します。
printfを使用したより堅牢なバージョンについては、以下を参照してください。

printlast(){
    shift "$1"
    echo "$1"
}

printlast "$#" "$@"          ### you may use ${1+"$@"} here to allow
                             ### a Bourne empty list of arguments,
                             ### but wait for a correct solution below.

コールこの構造はprintlastれる固定引数がシェルの引数のリストに設定する必要があり$1$2(引数スタック)等、及び所与として行わコール。

引数のリストを変更する必要がある場合は、それらを設定するだけです。

set -- o1e t2o t3r f4u
printlast "$#" "$@"

またはgetlast、汎用的な引数を許可するより使いやすい関数()を作成します(ただし、引数が2回渡されるため、それほど高速ではありません)。

getlast(){ printlast "$#" "$@"; }
getlast o1e t2o t3r f4u

(printlast のgetlast、またはに含まれるすべての)引数に$@はスペース、改行などが含まれる可能性があることに注意してください。

より良い

このバージョンは0、引数のリストが空の場合は印刷せず、より堅牢なprintfを使用します(古いシェルでecho外部printfが使用できない場合はフォールバックします)。

printlast(){ shift  "$1"; printf '%s' "$1"; }
getlast  ()  if     [ $# -gt 0 ]
             then   printlast "$#" "$@"
                    echo    ### optional if a trailing newline is wanted.
             fi
### The {} braces were removed on purpose.

getlast 1 2 3 4    # will print 4

EVALを使用します。

Bourneシェルがさらに古く、機能がない場合、または何らかの理由でevalを使用することが唯一のオプションである場合:

値を印刷するには:

if    [ $# -gt 0 ]
then  eval printf "'1%s\n'" \"\$\{$#\}\"
fi

変数に値を設定するには:

if    [ $# -gt 0 ]
then  eval 'last_arg='\"\$\{$#\}\"
fi

関数でそれを行う必要がある場合は、引数を関数にコピーする必要があります。

print_last_arg () {
    local last_arg                ### local only works on more modern shells.
    eval 'last_arg='\"\$\{$#\}\"
    echo "last_arg3=$last_arg"
}

print_last_arg "$@"               ### Shell arguments are sent to the function.

それはより良いですが、それはループを使用しないと言います-しかしそれは使用します。配列のコピーはループです。2つの関数を使用して、2つのループを使用します。また-配列全体を反復するほうがそれを使ってインデックスを付けるよりも望ましいはずなのevalですか?
mikeserv 2016年

1
何か見えますfor loopwhile loop


1
あなたの標準ではecho "Hello"、CPUがループ内のメモリ位置を読み取る必要があるため、すべてのコードにループがあると思います。繰り返しますが、私のコードにはループが追加されていません。

1
私はあなたの善悪の意見を本当に気にしません、それはすでに何度か間違っていることが証明されています。繰り返しますが、私のコードにはforwhileまたはuntilループがありません。あなたはどのご覧くださいfor whileまたはuntilコードで記述されたループを?。いいえ、それからただ事実を受け入れ、それを受け入れ、学びます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.