/ Bin / sh işlevine son argüman nasıl alınır


11

Uygulamanın daha iyi bir yolu nedir print_last_arg?

#!/bin/sh

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

print_last_arg foo bar baz
# baz

(Diyelim ki, ne yapmam gerektiğini bilmek #!/usr/bin/zshyerine #!/bin/sh. Sorunum bunu uygulamak için makul bir yol bulmak #!/bin/sh.)

EDIT: Yukarıdaki sadece saçma bir örnektir. Amacım son argümanı basmak değil , bir kabuk fonksiyonundaki son argümana atıfta bulunmanın bir yolunu bulmak.


EDIT2: Bu kadar belirsiz ifade edilen bir soru için özür dilerim. Bu sefer doğru yapmayı umuyorum.

Bu olsaydı /bin/zshyerine /bin/sh, böyle bir şey yazabilirsiniz

#!/bin/zsh

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

İfade $argv[$#], bir kabuk işlevi içindeki son argümana başvurmanın bir yolu olarak ilk EDIT'imde tanımladığım şeyin bir örneğidir .

Bu nedenle, orijinal örneğimi böyle yazmalıydım:

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

... daha sonra olduğum şeyin görevin sağına koymak için daha az korkunç bir şey olduğunu netleştirmek için.

Bununla birlikte, tüm örneklerde, son argümana tahribatsız olarak erişildiğini unutmayın . IOW, son argümana erişim konumsal argümanları bir bütün olarak etkilemez.


unix.stackexchange.com/q/145522/117549 , #! / bin / sh'ın çeşitli olasılıklarına dikkat çekiyor - herhangi birini daraltabilir misiniz?
Jeff Schaller

Ben düzenleme gibi, ama belki de burada son argüman referans yıkıcı olmayan bir yol sunuyor bir cevap olduğunu fark etmiş olabilirsiniz ...? Eğer eşit olmamalı var=$( eval echo \${$#})için eval var=\${$#}iki benzer bir şey değildir -.
mikeserv

1
Son notunuzu aldığınızdan emin değilim ama şimdiye kadar verilen hemen hemen tüm cevaplar çalışan kod argümanlarını korudukları için yıkıcı değil. Yalnızca shiftve set -- ...temelli çözümler, zararsız oldukları işlevlerde kullanılmadıkça yıkıcı olabilir.
jlliagre

@jlliagre - ancak ana kısımda hala yıkıcıdırlar - keşfedilmeleri için yok edebilmeleri için tek kullanımlık bağlamlar oluşturmayı gerektirirler. ama ... yine de ikinci bir bağlam alırsanız - neden sadece bir dizin oluşturmanıza izin vermiyorsunuz? işe yönelik aracı kullanmakla ilgili bir sorun var mı? kabuk genişletmelerini genişletilebilir girdi olarak yorumlamak eval'ün işidir. ve bunun garantili güvenli bir değer eval "var=\${$#}"olması var=${arr[evaled index]}dışında, yapmaktan önemli ölçüde farklı bir şey yoktur $#. neden tüm seti kopyalayıp doğrudan indeksleyebiliyorsanız onu yok etmelisiniz?
mikeserv

1
@mikeserv Kabuğun ana bölümünde yapılan döngü için tüm argümanlar değişmeden kalır. Tüm argümanları döngüye almanın çok optimize edilmediğini kabul ediyorum, özellikle de binlerce tanesi kabuğa geçirilirse ve son argümana doğrudan doğru indeksle erişmenin en iyi cevap olduğunu kabul ediyorum (ve neden indirildiğini anlamıyorum) ) ancak bunun ötesinde, gerçekten yıkıcı bir şey yoktur ve fazladan bir bağlam yaratılmamıştır.
jlliagre

Yanıtlar:


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

... dize ve the last arg isardından bir <boşluk> , son bağımsız değişkenin değeri ve en az 1 bağımsız değişken varsa bir son <newline> yazdıracak ya da sıfır bağımsız değişken için hiçbir şey yazdırmayacak.

Eğer yaptıysanız:

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

... ya $lastargen az 1 bağımsız değişken varsa , son bağımsız değişkenin değerini shell değişkenine atarsınız ya da hiçbir şey yapmazsınız. Her iki durumda da, bunu güvenli bir şekilde yaparsınız ve bence Olde Bourne kabuğuna bile taşınabilir olmalı.

Tüm arg dizisinin iki kez kopyalanmasını gerektirmesine rağmen (ve Bourne kabuğu için bir printfgiriş$PATH gerektiriyorsa da) benzer şekilde çalışan başka bir tane :

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

Yorumlar uzun tartışmalar için değildir; bu görüşme sohbete taşındı .
terdon

2
Bilginize, ilk öneriniz eski bir bourne kabuğu altında "hatalı değiştirme" hatası vererek başarısız olur. Kalan her ikisi de beklendiği gibi çalışır.
jlliagre

@jillagre - teşekkür ederim. o ilk biri hakkında o kadar emin değildi - ama diğer ikisinden oldukça emindi. sadece argümanlara referans satır içi ile nasıl erişilebildiğinin gösterilmesi gerekiyordu. tercihen bir fonksiyon ${1+":"} returnhemen açık gibi bir şeyle açılabilir - çünkü kim bir şey yapmayı veya herhangi bir şey için çağrılmışsa her türlü yan etkiyi riske atmayı ister? Matematik aynı şeydir - eğer pozitif bir tamsayıyı genişletebileceğinizden emin olabilirseniz, evalbütün gün yapabilirsiniz . $OPTINDbunun için harika.
mikeserv

3

İşte basit bir yol:

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

(@ cuonglm'in, argüman iletilmediğinde orijinalin başarısız olduğu noktasına göre güncellenir; bu şimdi boş bir satır yansıtır - istenirse yan tümcedeki davranışı değiştirin else)


Solaris'te / usr / xpg4 / bin / sh; Solaris #! / bin / sh bir gereklilik olup olmadığını bana bildirin.
Jeff Schaller

Bu soru yazmaya çalıştığım belirli bir komut dosyasıyla ilgili değil, genel bir nasıl yapılır. Bunun için iyi bir tarif arıyorum. Tabii ki, daha taşınabilir, daha iyi.
kjo

$ (()) matematik Solaris / bin / sh'de başarısız olur; şuna benzer bir şey kullanın: gerekirse "ifade $ # - 1" kaydırın.
Jeff Schaller

alternatif olarak Solaris / bin / sh için,echo "$# - 1" | bc
Jeff Schaller

1
tam olarak yazmak istediğim şeydi, ama denediğim 2. kabukta başarısız oldu - görünüşte desteklemeyen bir Almquist kabuğu olan NetBSD :(
Jeff Schaller

3

Açılış yazısı örneği göz önüne alındığında (boşluksuz konumsal bağımsız değişkenler):

print_last_arg foo bar baz

Varsayılan olarak IFS=' \t\n', nasıl olur:

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

Daha güvenli bir genişleme için "$*"IFS'yi ayarlayın (@ StéphaneChazelas başına):

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

Ancak konum bağımsız değişkenleriniz boşluk içerebiliyorsa yukarıdakiler başarısız olacaktır. Bu durumda, bunun yerine şunu kullanın:

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

Bu tekniklerin kullanılmasını önlediğini evalve yan etkileri olmadığını unutmayın.

Shellcheck.net adresinde test edildi


1
Son argüman boşluk içeriyorsa, ilk hata başarısız olur.
cuonglm

1
İlki POSIX öncesi kabukta da çalışmadı
cuonglm

@cuonglm İyi tespit, doğru gözleminiz şimdi dahil edilmiştir.
AsymLabs

Ayrıca ilk karakterinin $IFSbir boşluk olduğunu varsayar .
Stéphane Chazelas

1
Çevrede $ IFS yoksa, aksi belirtilmedikçe olacaktır. Ancak split + glob operatörünü neredeyse her kullandığınızda IFS'yi ayarlamanız gerektiğinden (bir genişletmeyi ayrılmamış olarak), IFS ile başa çıkmanın bir yolu, ihtiyacınız olduğunda onu ayarlamaktır. Sadece IFS=' 'genişlemesi için kullanıldığını açıklığa kavuşturmak burada olması zarar vermez "$*".
Stéphane Chazelas

3

Bu soru 2 yaşın üzerinde olmasına rağmen, biraz daha kompakt bir seçeneği paylaşacağımı düşündüm.

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

Hadi koşalım

print_last_arg foo bar baz
baz

Bash kabuğu parametre genişletmesi .

Düzenle

Daha da özlü: echo "${@: -1}"

(Boşluğa dikkat edin)

Kaynak

MacOS 10.12.6 üzerinde test edilmiştir, ancak aynı zamanda en mevcut * nix aromaları hakkındaki son argümanı da döndürmelidir ...

Çok daha az acıyor ¯\_(ツ)_/¯


Bu kabul edilen cevap olmalı. Daha da iyisi olacaktır: echo "${*: -1}"hangi shellcheckşikayetçi olmaz.
Tom Hale

4
Bu düz bir POSIX sh ile çalışmaz ${array:n:m:}, bir uzantıdır. (soru açıkça belirtti /bin/sh)
ilkkachu

2

POSIXly:

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

printf '%s\n' "$1"

(Bu yaklaşım eski Bourne kabuğunda da işe yarar)

Diğer standart araçlarla:

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

(Bu eski ile çalışmaz awk, ki bu yoktu ARGV)


Hala bir Bourne kabuğunun olduğu yerde, awksahip olmayan eski awk de olabilir ARGV.
Stéphane Chazelas

@ StéphaneChazelas: Kabul et. En azından Brian Kernighan'ın kendi versiyonuyla çalıştı. İdealin var mı?
cuonglm

Bu argümanları siler. Onları nasıl saklayabilirim?

@BinaryZebra: awkYaklaşımı kullanın ya da diğerleri forgibi düz bir döngü kullanın ya da bir işleve geçirin.
cuonglm

2

Bu, POSIX uyumlu herhangi bir kabukla çalışmalı ve önceki POSIX eski Solaris Bourne kabuğuyla da çalışacaktır:

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

ve işte aynı yaklaşıma dayanan bir fonksiyon:

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

PS: bana fonksiyon vücut etrafında kıvırcık parantez unuttum söyleme ;-)


2
Fonksiyon gövdesi etrafındaki kıvırcık parantezleri unuttun ;-).

@BinaryZebra Sizi uyardım ;-) Onları unutmadım. Diş telleri burada şaşırtıcı derecede isteğe bağlıdır.
jlliagre

1
@jlliagre Gerçekten uyarıldım ;-): P ...... Ve: kesinlikle!

Sözdizimi spesifikasyonunun hangi kısmı buna izin veriyor?
Tom Hale

Kabuk gramer kuralları @TomHale hem eksik veriyor in ...bir de fordöngü ve bir çevrede bir hayır küme parantezleri ifişlev organı olarak kullanılan açıklamada. Bkz for_clauseve compound_commandiçinde pubs.opengroup.org/onlinepubs/9699919799 .
jlliagre

2

Gönderen "- Sıkça Sorulan Sorular Unix"

(1)

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

Bağımsız değişken sayısı sıfır olabilirse, sıfır bağımsız değişkeni $0(genellikle komut dosyasının adı) atanır $last. Eğer olmasının nedeni budur.

(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"

Bağımsız değişken olmadığında boş bir satır yazdırmamak için echo "$last"for öğesini değiştirin :

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

Sıfır argüman sayısından kaçınılır if [ $# -gt 0 ].

Bu, sayfadaki bağlantıda bulunanların tam bir kopyası değildir, bazı iyileştirmeler eklenmiştir.


0

Bu, perlkurulu tüm sistemlerde çalışmalıdır (çoğu UNICES):

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

Hüner kullanmaktır printfbir ekleme \0her kabuk argüman ve sonra sonra perl'nin -0NULL olarak onun rekor ayırıcı ayarlar anahtarı. Daha sonra girdi üzerinde yineleme yaparız, \0her NULL sonlandırılmış satırı olarak kaydeder ve kaydederiz $l. ENDBlok (en şey bu }{son kabuk argüman: is) tüm giriş son "satırını" yazdırması amacıyla okunduktan sonra çalıştırılacaktır.


@mikeserv ah, doğru, son argüman dışında hiçbir yeni satırla test yapmamıştım. Düzenlenmiş sürüm hemen hemen her şey için çalışmalıdır, ancak muhtemelen böyle basit bir görev için çok kıvrımlıdır.
terdon

evet, dışarıdan bir çalıştırılabilir dosyayı çağırmak (eğer zaten mantığın bir parçası değilse) , bana göre biraz ağırdır. nokta için o argüman geçmek ise ancak sed, awkya perlo zaman zaten değerli bilgiler olabilir. sed -zgüncel GNU sürümleri için de aynısını yapabilirsiniz .
mikeserv

neden indirildin? bu iyiydi ... diye düşündüm?
mikeserv

0

İşte özyineleme kullanan bir sürüm. POSIX'in bunun nasıl uyumlu olduğu hakkında hiçbir fikrim yok ...

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

0

Basit bir kavram, aritmetik yok, döngü yok, eval yok , sadece fonksiyonlar.
Bourne kabuğunun aritmetik (harici gerekli expr) olmadığını unutmayın . Aritmetik ücretsiz, evalücretsiz bir seçim almak istiyorsanız , bu bir seçenektir. İşlevlere ihtiyaç duyulması, SVR3 veya üstü anlamına gelir (parametrelerin üzerine yazılmaz).
Printf ile daha sağlam bir sürüm için aşağıya bakın.

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.

Aramaya Bu yapı printlastolup , sabit argümanlar kabuk bağımsız değişkenleri listesinde ayarlanması gerekir, $1, $2vb (argüman yığını) ile verilen arama yapılır.

Bağımsız değişken listesinin değiştirilmesi gerekiyorsa, bunları ayarlayın:

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

Veya getlastgenel argümanlara izin verebilecek (ancak hızlı değil, argümanlar iki kez iletilir) kullanımı daha kolay bir işlev ( ) oluşturun.

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

Lütfen ( getlastveya $@printlast için dahil edilen ) bağımsız değişkenlerin boşluk, yeni satır vb. Olabileceğini, ancak NUL olmadığını unutmayın.

Daha iyi

Bu sürüm, 0argümanlar listesi boş olduğunda yazdırılmaz ve daha sağlam printf kullanın ( eski kabuklar için echoharici printfkullanılamıyorsa geri dönün ).

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 kullanma.

Bourne kabuğu daha da eskiyse ve herhangi bir işlev yoksa veya eval nedense tek seçenekse:

Değeri yazdırmak için:

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

Bir değişkendeki değeri ayarlamak için:

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

Bunun bir işlevde yapılması gerekiyorsa, bağımsız değişkenlerin işleve kopyalanması gerekir:

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.

daha iyi ama bir döngü kullanmadığını söylüyor - ama kullanıyor. dizi kopyası bir döngüdür . ve iki işlevi ile iki döngü kullanır . Ayrıca - dizinin tamamını dizine eklemenin bir nedeni var evalmı?
mikeserv

1
Eğer bir şey görüyor musunuz for loopya while loop?


1
Standart echo "Hello"olarak, CPU bir döngüdeki bellek konumlarını okumak zorunda olduğu için tüm kodların döngüler, hatta bir döngüler vardır sanırım . Tekrar: kodumda ek döngüler yok.

1
İyi ya da kötü fikrinizi umursamıyorum, zaten birkaç kez yanlış olduğu kanıtlandı. Tekrar: kodum hiçbir vardır for, whileya da untildöngüler. Kodda yazılmış bir döngü for whileveya untildöngü görüyor musunuz ? Hayır, o zaman sadece gerçeği kabul et, kucakla ve öğren.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.