ShellScript
Bash

シェルスクリプトで最後の引数 or 要素

More than 5 years have passed since last update.

最後の引数 or 要素を出力する方法を色々試してみた。


引数編

設定:set -- "1a" "2b" "3c"

以下のコマンドはいずれも 3c を出力する。


sh

echo ${!#}

echo ${@:$#}
eval echo \
$$#
unset last; for last in
"$@"; do :; done; echo $last
: "" "$@"; echo $_
(shift
$(($# - 1)); echo $@)


<処理内容>


${!#}

二段階展開(?)。先に $# が展開されて $n が展開される。※ZSH 不可


${@:offset:length}

bash の場合、$# と最後の引数のオフセットがイコールなので $#。残りが無いので length は不要。この部分は算術式なので ${@:$#-1:1} とすれば $#-1 の結果から最後から2番目を指定する。※ ZSH 不可。代わりに $@[-1] が使える。


eval \$$# または eval '$'$#

${!#} とだいたい同じ。


for

ちょっと無駄な作業。変数をリセットしなければ最後のループで使った値が残るのを応用。変数を事前にリセットしておく必要あり。試しに引数を1万個にしてやってみたけど出力にかかる時間は 0.1 秒だった。


_

前のコマンドの最後の引数が入っている変数。:(=true)で左辺は展開するだけで何もしない。"$@" を展開したときに空だった場合、_ には : がセットされるためダミーとして : の第1引数に "" をセットしておく。


(shift ~)

サブシェル内では shift しても親の引数に影響を与えない。最後の引数の1つ前までを shift し、残り(最後の引数)を出力する。


引数の確認が必要かもしれないパターン

以下のパターンは引数が 0 のときに備えて test と併用した方がいいかもしれない。

コマンド
結果

${!#}
eval \$$#

$0 を展開(/bin/bash など)する。

(shift $(($# - 1)); echo $@)
算術式の結果が負の値になるため shift が 1 で終了する。セミコロンで繋いだ場合は echo $@ は空でサブシェルの終了ステータスは 0。


配列変数編

設定:foo=("1a" "2b" "3c")

引数編とだいたい同じ。配列のオフセットは bash は 0 から、zsh は 1 から。以下のコマンドはいずれも 3c を出力する。


sh

echo ${foo[${#foo[@]}-1]} # ZSH では $foo[$#foo] または $foo[-1]

echo ${foo[@]:${#foo[@]}-1} # ZSH 不可
: "" "${foo[@]}"; echo $_
(set -- "${foo[@]}"; echo ${@:$#})
sh -c
'echo ${@:$#}' -- "${foo[@]}"
unset last; for last in
"${foo[@]}"; do :; done; echo $last
lastarg (){ echo
${@:$#}; }; lastarg "${foo[@]}"


<処理内容>

同じ方法を用いているものは省略。要素数が 0 のときに空を出力しないパターンは引数編と同様。


(set -- "${foo[@]}"; echo ${@:$#})

配列をサブシェル内の引数に変換する。サブシェル内の $@ および $# はサブシェル内の引数を使用する。


sh -c 'echo ${@:$#}' -- "${foo[@]}"

sh の引数にして出力する。コマンド部分にはシングルクオートを用いるか、$ をエスケープする。


lastarg (){ echo ${@:$#}; }; lastarg "${foo[@]}"

関数を作成し、関数内で最後の引数を処理する。