Durum bilgisi olan bash işlevi


16

Bash'te her çağrıda bir sayıyı artıran (ve döndüren) bir işlev uygulamak istiyorum. Ne yazık ki bu önemsiz gibi görünüyor çünkü bir alt kabuk içindeki işlevi çağırıyorum ve sonuç olarak üst kabuğunun değişkenlerini değiştiremiyor.

İşte benim girişimim:

PS_COUNT=0

ps_count_inc() {
    let PS_COUNT=PS_COUNT+1
    echo $PS_COUNT
}

ps_count_reset() {
    let PS_COUNT=0
}

Bu aşağıdaki gibi kullanılır (ve bu nedenle fonksiyonları bir alt kabuktan çağırmam gerekir):

PS1='$(ps_count_reset)> '
PS2='$(ps_count_inc)   '

Bu şekilde, numaralandırılmış bir çok satırlı istemim olurdu:

> echo 'this
1   is
2   a
3   test'

Şirin. Ancak yukarıda belirtilen sınırlama nedeniyle çalışmaz.

Çalışmayan bir çözüm, sayıyı değişken yerine bir dosyaya yazmak olacaktır. Ancak, bu, aynı anda çalışan birden çok oturum arasında bir çakışma yaratacaktır. Tabii ki, kabuğun işlem kimliğini dosya adına ekleyebilirim. Ancak, sistemimi çok sayıda dosyayla karıştırmayacak daha iyi bir çözüm olduğunu umuyorum.


WRT çarpışmalarını kullanarak bir dosya saklamak bakın man 1 mktemp.
goldilocks

Düzenlememi görmelisin - bence beğeneceksin.
mikeserv

Yanıtlar:


14

resim açıklamasını buraya girin

Sorunuzda not ettiğiniz aynı çıktıyı elde etmek için gereken tek şey şudur:

PS1='${PS2c##*[$((PS2c=0))-9]}- > '
PS2='$((PS2c=PS2c+1)) > '

Bükülmenize gerek yok. Bu iki satır, POSIX uyumluluğuna yakın herhangi bir şey gibi davranan herhangi bir kabukta bunu yapacak.

- > cat <<HD
1 >     line 1
2 >     line $((PS2c-1))
3 > HD
    line 1
    line 2
- > echo $PS2c
0

Ama bunu sevdim. Ve bunu daha iyi yapan şeyin temellerini göstermek istedim. Bu yüzden biraz düzenledim. /tmpŞimdilik takıldım ama sanırım kendim için de saklayacağım. Burada:

cat /tmp/prompt

İSTEMİ SENARYO:

ps1() { IFS=/
    set -- ${PWD%"${last=${PWD##/*/}}"}
    printf "${1+%c/}" "$@" 
    printf "$last > "
}

PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'
PS2='$((PS2c=PS2c+1)) > '

Not: son zamanlarda yaş'ı öğrendikten sonra , dün inşa ettim. Herhangi bir nedenle, her argümanın ilk baytını %cdize ile basmaz - dokümanlar bu format için geniş karakter uzantıları hakkında spesifik olsa da ve belki de ilgili olabilir - ancak%.1s

Her şey bu. Orada iki ana şey var. Ve işte böyle görünüyor:

/u/s/m/man3 > cat <<HERE
1 >     line 1
2 >     line 2
3 >     line $((PS2c-1))
4 > HERE
    line 1
    line 2
    line 3
/u/s/m/man3 >

Çözümleme $PWD

Her $PS1değerlendirildiğinde, isteme $PWDeklemek için ayrıştırılır ve yazdırılır . Ama tüm $PWDekranımı doldurmayı sevmiyorum , bu yüzden tam olarak görmek istediğim geçerli dizine kadar geçerli yoldaki her kırıntısının ilk harfini istiyorum. Bunun gibi:

/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cd /
/ > cd ~
/h/mikeserv > 

Burada birkaç adım var:

IFS=/

mevcut $PWDve en güvenilir yolu $IFSbölmek zorundayız /. Daha sonra onunla uğraşmanıza gerek yok - buradan itibaren tüm bölünmeler, kabuğun bir $@sonraki komuttaki konumsal parametre dizisi tarafından tanımlanır :

set -- ${PWD%"${last=${PWD##/*/}}"}

Bu biraz zor, ama asıl mesele semboller $PWDüzerinde bölünüyor olmamız /. Ayrıca $last, en sol ve en sağdaki /eğik çizgi arasında oluşan herhangi bir değerden sonra her şeye atamak için parametre genişletme özelliğini kullanırım . Bu şekilde, eğer sadece bir tanesiysem /ve sadece bir tanesine /sahipsem $last, yine de bütüne eşit $PWDve $1boş kalacağını biliyorum. Bu önemli. Ayrıca atamadan önce $lastkuyruk ucundan $PWDsoyuyorum $@.

printf "${1+%c/}" "$@"

Yani burada - sürece ${1+is set}biz printfilk %cher bizim kabuğun argümanlardan haracter - biz sadece mevcut her dizine kurdum $PWD- daha az üst dizin - on bölünmüş /. Bu yüzden aslında sadece her dizinin ilk karakterini $PWDdeğil, ilk dizini yazdırıyoruz . Ancak $1bunun, kökte /veya içinde /olduğu gibi kaldırılmayacak şekilde ayarlanması durumunda gerçekleştiğini fark etmek önemlidir /etc.

printf "$last > "

$lastaz önce üst dizinimize atadığım değişken. Şimdi bu bizim üst dizinimiz. Son ifadenin yapılıp yapılmadığını yazdırır. >İyi bir önlem için çok az şey gerekiyor .

ANCAK ENCREMENT HAKKINDA NELER VAR?

Bir de $PS2şartlılık meselesi var . Daha önce aşağıda bulabileceğiniz bunun nasıl yapılabileceğini daha önce göstermiştim - bu temelde bir kapsam meselesidir. Ancak, bir sürü printf \backpace yapmaya başlamak ve daha sonra karakter sayısını dengelemeye çalışmak istemiyorsanız, biraz daha var ... ugh. Ben de bunu yapıyorum:

PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'

Yine ${parameter##expansion}günü kurtarır. Yine de burada biraz garip - biz kendi değişken şerit aslında değişken ayarlamak. Yeni değerini - orta şerit olarak ayarladık - soyduğumuz küre olarak kullanıyoruz. Anlıyorsun? Biz ##*herhangi bir şey olabilir son karakteri bizim artım değişkenin kafasından tüm şerit [$((PS2c=0))-9]. Bu şekilde değerin çıktısını vermememiz garanti edilir, ancak yine de onu atarız. Oldukça havalı - bunu daha önce hiç yapmadım. Ancak POSIX, bunun yapılabilecek en taşınabilir yol olduğunu da bize garanti ediyor.

POSIX tarafından belirlendiğinden ${parameter} $((expansion)), bu tanımları nerede değerlendirdiğimizden bağımsız olarak ayrı bir alt kabuk içine koymamıza gerek kalmadan mevcut kabukta tutar. O çalışır İşte bu yüzden dashve shsadece de o olduğu gibi bashve zsh. Kabuk / terminale bağımlı kaçış kullanmıyoruz ve değişkenlerin kendilerini test etmesine izin veriyoruz. Taşınabilir kodu hızlı yapan da budur .

Gerisi oldukça basittir - her seferinde sayacımızı arttırmak $PS2, bir $PS1kez daha sıfırlanana kadar değerlendirilir . Bunun gibi:

PS2='$((PS2c=PS2c+1)) > '

Şimdi şunları yapabilirim:

DASH DEMO

ENV=/tmp/prompt dash -i

/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cat <<HERE
1 >     line 1
2 >     line 2
3 >     line $((PS2c-1))
4 > HERE
    line 1
    line 2
    line 3
/u/s/m/man3 > printf '\t%s\n' "$PS1" "$PS2" "$PS2c"
    $(ps1)${PS2c##*[$((PS2c=0))-9]}
    $((PS2c=PS2c+1)) >
    0
/u/s/m/man3 > cd ~
/h/mikeserv >

SH DEMO

Aynı şekilde çalışır bashveya sh:

ENV=/tmp/prompt sh -i

/h/mikeserv > cat <<HEREDOC
1 >     $( echo $PS2c )
2 >     $( echo $PS1 )
3 >     $( echo $PS2 )
4 > HEREDOC
    4
    $(ps1)${PS2c##*[$((PS2c=0))-9]}
    $((PS2c=PS2c+1)) >
/h/mikeserv > echo $PS2c ; cd /
0
/ > cd /usr/share
/u/share > cd ~
/h/mikeserv > exit

Yukarıda söylediğim gibi, asıl sorun, hesaplamanızı nerede yaptığınızı düşünmeniz gerektiğidir. Üst kabukta durumu alamazsınız, böylece orada hesaplama yapmazsınız. Durumu alt kabukta alırsınız - işte bu noktada hesaplarsınız. Ancak tanımı üst kabukta yaparsınız.

ENV=/dev/fd/3 sh -i  3<<\PROMPT
    ps1() { printf '$((PS2c=0)) > ' ; }
    ps2() { printf '$((PS2c=PS2c+1)) > ' ; }
    PS1=$(ps1)
    PS2=$(ps2)
PROMPT

0 > cat <<MULTI_LINE
1 > $(echo this will be line 1)
2 > $(echo and this line 2)
3 > $(echo here is line 3)
4 > MULTI_LINE
this will be line 1
and this line 2
here is line 3
0 >

1
@mikeserv Daireler çiziyoruz. Bütün bunları biliyorum. Ama bunu tanımımda nasıl kullanırım PS2? Bu zor kısmı. Çözümünüzün burada uygulanabileceğini sanmıyorum. Aksini düşünüyorsanız lütfen bana nasıl olduğunu gösterin.
Konrad Rudolph

1
@mikeserv Hayır, ilgisiz, üzgünüm. Ayrıntılar için soruma bakın. PS1ve PS2kabukta komut istemi olarak yazdırılan özel değişkenlerdir ( PS1yeni bir kabuk penceresinde farklı bir değere ayarlayarak deneyin ), böylece kodunuzdan çok farklı kullanılırlar. Kullanımları hakkında daha fazla bilgi: linuxconfig.org/bash-prompt-basics
Konrad Rudolph

1
@KonradRudolph Onları iki kez tanımlamanızı engelleyen nedir? Benim asıl işim buydu ... Cevabınıza bakmak zorundayım ... Bu her zaman yapılır.
mikeserv

1
@mikeserv echo 'thisBir komut istemi yazın, ardından PS2kapanış tekli alıntıyı yazmadan önce değerinin nasıl güncelleneceğini açıklayın .
chepner

1
Tamam, bu cevap şimdi resmen şaşırtıcı. Ayrıca ekmek kırıntılarını da seviyorum, ancak yine de tam yolu ayrı bir satıra yazdırdığım için kabul etmeyeceğim: i.imgur.com/xmqrVxL.png
Konrad Rudolph

8

Bu yaklaşımla (bir alt kabukta çalışan fonksiyon), ana kabuk işleminin durumunu, bükülmelerden geçmeden güncelleyemezsiniz. Bunun yerine, işlevin ana işlemde çalışmasını sağlayın.

PROMPT_COMMANDDeğişkenin değeri, bilgi PS1istemi yazdırılmadan önce yürütülen bir komut olarak yorumlanır .

Çünkü PS2karşılaştırılabilir bir şey yok. Ancak bunun yerine bir hile kullanabilirsiniz: tüm yapmak istediğiniz bir aritmetik işlem olduğundan, alt kabuk içermeyen aritmetik genişletmeyi kullanabilirsiniz.

PROMPT_COMMAND='PS_COUNT=0'
PS2='$((++PS_COUNT))  '

Aritmetik hesaplamanın sonucu bilgi isteminde sona erer. Gizlemek istiyorsanız, var olmayan bir dizi alt kodu olarak iletebilirsiniz.

PS1='${nonexistent_array[$((PS_COUNT=0))]}\$ '

4

Biraz G / Ç yoğun, ancak sayının değerini tutmak için geçici bir dosya kullanmanız gerekir.

ps_count_inc () {
   read ps_count < ~/.prompt_num
   echo $((++ps_count)) | tee ~/.prompt_num
}

ps_count_reset () {
   echo 0 > ~/.prompt_num
}

Kabuk oturumu başına ayrı bir dosyaya ihtiyacınız varsa (küçük bir endişe gibi görünüyor; gerçekten aynı anda iki farklı kabukta çok satırlı komutlar yazacak mısınız?), mktempHer biri için yeni bir dosya oluşturmak için kullanmalısınız . kullanın.

ps_count_reset () {
    rm -f "$prompt_count"
    prompt_count=$(mktemp)
    echo 0 > "$prompt_count"
}

ps_count_inc () {
    read ps_count < "$prompt_count"
    echo $((++ps_count)) | tee "$prompt_count"
}

+1 G / Ç muhtemelen çok önemli değildir, çünkü dosya küçükse ve sık erişiliyorsa, önbelleğe alınır, yani esasen paylaşılan bellek olarak işlev görür.
goldilocks

1

Sen yapamazsın bir kabuk değişkeni bu şekilde kullanmak ve zaten anlamak. Alt kabuk, değişkenleri bir sürecin çevresini miras aldığı gibi miras alır: yapılan değişiklikler, herhangi bir ata sürecine değil, sadece ona ve onun çocuklarına uygulanır .

Diğer cevaplara göre yapılacak en kolay şey bir dosyadaki verileri saklamak.

echo $count > file
count=$(<file)

Vb.


Elbette bu şekilde bir değişken ayarlayabilirsiniz. Geçici bir dosyaya ihtiyacınız yoktur. Alt kabuktaki değişkeni ayarlarsınız ve değerini bu değeri emdiğiniz üst kabuğa yazdırırsınız. Alt kabuktaki değerini hesaplamak için gereken tüm durumu elde edersiniz, böylece bunu yaparsınız.
mikeserv

1
@mikeserv Bu aynı şey değil, bu yüzden OP böyle bir çözümün işe yaramayacağını söyledi (bu soruda daha açık hale getirilmiş olsa da). Bahsettiğiniz, IPC üzerinden başka bir işleme bir değer iletmektir, böylece bu değeri herhangi bir değere atayabilir. OP'nin yapmak istediği / ihtiyaç duyduğu şey, bir dizi işlem tarafından paylaşılan bir global değişkenin değerini etkiledi ve bunu çevre yoluyla yapamazsınız; IPC için çok yararlı değildir.
goldilocks

Dostum, ya burada neye ihtiyaç duyulduğunu tamamen yanlış anladım ya da herkesin sahip olduğu. Benim için gerçekten basit görünüyor. Düzenlememi görüyor musun? Bunun nesi var?
mikeserv

@mikeserv Yanlış anladığınızı ve adil olduğunuzu düşünmüyorum, sahip olduğunuz şey bir IPC biçimidir ve işe yarayabilir. Konrad'ın neden sevmediği açık değil, ancak yeterince esnek değilse, dosya saklaması oldukça basittir (ve çarpışmalardan kaçınmanın yolları da vardır mktemp).
goldilocks

2
@mikeserv İstenen işlev, değeri PS2kabuk tarafından genişletildiğinde çağrılır . O anda üst kabuktaki bir değişkenin değerini güncelleme fırsatınız yoktur.
chepner

0

Referans olarak, işte kabuk işlemi başına benzersiz olan ve mümkün olan en kısa sürede (soruda belirtildiği gibi dağınıklığı önlemek için) silinen geçici dosyaları kullanan çözümüm:

# Yes, I actually need this to work across my systems. :-/
_mktemp() {
    local tmpfile="${TMPDIR-/tmp}/psfile-$$.XXX"
    local bin="$(command -v mktemp || echo echo)"
    local file="$($bin "$tmpfile")"
    rm -f "$file"
    echo "$file"
}

PS_COUNT_FILE="$(_mktemp)"

ps_count_inc() {
    local PS_COUNT
    if [[ -f "$PS_COUNT_FILE" ]]; then
        let PS_COUNT=$(<"$PS_COUNT_FILE")+1
    else
        PS_COUNT=1
    fi

    echo $PS_COUNT | tee "$PS_COUNT_FILE"
}

ps_count_reset() {
    rm -f "$PS_COUNT_FILE"
}
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.