Bir kabuk komutunun çıktısındaki karakter sayısı


12

Tek bir adımda bir komutun çıkışındaki karakter sayısını hesaplaması gereken bir komut dosyası yazıyorum .

Örneğin, komutun kullanılması 10 karakter uzunluğunda olduğu için komutun kullanılması readlink -f /etc/fstabgerekir 10.

Bu, aşağıdaki kod kullanılarak saklanan değişkenlerle zaten mümkündür:

variable="somestring";
echo ${#variable};
# 10

Ne yazık ki, aynı formülü komut tarafından oluşturulan bir dizeyle kullanmak işe yaramaz:

${#(readlink -f /etc/fstab)};
# bash: ${#(readlink -f /etc/fstab)}: bad substitution

İlk önce bir değişkene çıktı kaydederek bunu yapmak mümkün olduğunu anlıyorum:

variable=$(readlink -f /etc/fstab);
echo ${#variable};

Ama fazladan adımı kaldırmak istiyorum.

Mümkün mü? Yalnızca dahili veya standart yardımcı programları kullanan Almquist kabuğu (sh) ile uyumluluk tercih edilir.


1
Çıktısı readlink -f /etc/fstabolan 11 karakter. Yeni satırı unutmayın. Aksi takdirde /etc/fstabluser@cern:~$ , onu bir kabuktan ne zaman çalıştırdığınızı görürsünüz .
Phil Frost

Komik bir istemi var gibi görünüyor, CERN'de mi çalışıyorsun?
Dmitry Grigoryev

Yanıtlar:


9

İle GNU İfade :

$ expr length + "$(readlink -f /etc/fstab)"
10

+GNU bir özelliği yoktur expremin sonraki argüman bir olur bile bir dize olarak kabul edilir hale getirmek için exprböyle operatör match, length, +...

Yukarıdaki herhangi bir yeni satırsonu çıkacaktır. Etrafında çalışmak için:

$ expr length + "$(readlink -f /etc/fstab; printf .)" - 2
10

Son 2 satır readlinkve .eklediğimiz karakter nedeniyle sonuç 2'ye çıkarıldı .

Unicode dize ile, expryerine karakterlerin bayt dize uzunluğunu döndürür çünkü, iş görünmüyor saymak (Bkz hattı 654 )

$ LC_ALL=C.UTF-8 expr length ăaa
4

Yani, şunları kullanabilirsiniz:

$ printf "ăaa" | LC_ALL=C.UTF-8 wc -m
3

POSIXLY:

$ expr " $(readlink -f /etc/fstab; printf .)" : ".*" - 3
10

Komut değiştirme işleminden önceki boşluk, komutun dize başlangıcı ile çökmesini engeller -, bu nedenle 3'ü çıkarmamız gerekir.


Teşekkürler! Üçüncü örneğiniz LC_ALL=C.UTF-8, dizenin kodlaması önceden bilinmeyecekse, işleri önemli ölçüde basitleştiren, olmadan da çalışıyor gibi görünüyor .
user339676

2
expr length $(echo "*")- Hayır. En azından çift tırnak kullanın: expr length "$(…)". Ancak bu, komut satırından gelen yeni satırları kaldırır, komut değiştirmenin kaçınılmaz bir özelliğidir. (Etrafında çalışabilirsin, ama sonra cevap daha da karmaşıklaşır.)
Gilles 'SO- kötü olmayı bırak'

6

Bunu kabuk yerleşikleriyle nasıl yapacağınızdan emin değilim ( Gnouc olsa da ), ancak standart araçlar yardımcı olabilir:

  1. wc -mHangi karakterlerin sayılacağını kullanabilirsiniz . Ne yazık ki, son satırsonunu da sayar, bu yüzden önce bundan kurtulmanız gerekir:

    readlink -f /etc/fstab | tr -d '\n' | wc -m
  2. Tabii ki kullanabilirsiniz awk

    readlink -f /etc/fstab | awk '{print length($0)}'
  3. Veya Perl

    readlink -f /etc/fstab | perl -lne 'print length'

Yerleşik mi demek istiyorsun expr? Hangi kabukta?
mikeserv

5

Genellikle böyle yaparım:

$ echo -n "$variable" | wc -m
10

Komutları yapmak için şöyle uyarlıyorum:

$ echo -n "$(readlink -f /etc/fstab)" | wc -m
10

Bu yaklaşım, 2 adımda yaptığınız şeye benzer, ancak bunları tek bir astarda birleştiriyoruz.


2
Bunun -myerine kullanmalısınız -c. Unicode karakterlerle yaklaşımınız bozulacaktır.
cuonglm

1
Neden basit değil readlink -f /etc/fstab | wc -m?
Phil Frost

1
Neden bu güvenilir olmayan yöntemi kullanıyorsunuz ${#variable}? En azından çift tırnak kullanmak echo -n "$variable", ancak bu hala ör eğer değerini başarısız variableolduğunu -e. Komut ikamesi ile birlikte kullandığınızda, son satırların çıkarıldığını unutmayın.
Gilles 'SO- kötü olmayı bırak'

@ philfrost b / c gösterdiğim şey op zaten ne düşündüğünü inşa. Ayrıca daha önce vars ayarlanmış olabilir cmds için çalışır ve uzunlukları afterwords istiyor. Ayrıca terdon zaten bu örneğe sahip.
slm

1

Harici yardımcı programları çağırabilirsiniz (diğer yanıtlara bakın), ancak komut dosyanızı yavaşlatacaklar ve tesisat işlemini doğru yapmak zor.

zsh

Zsh'de, ${#$(readlink -f /etc/fstab)}komut değişikliğinin uzunluğunu almak için yazabilirsiniz . Bunun komut çıktısının uzunluğu olmadığını, herhangi bir satırsonu olmadan çıkışın uzunluğu olduğunu unutmayın.

Çıktının tam uzunluğunu istiyorsanız, sonunda fazladan yeni olmayan bir karakter çıktılayın ve bir tane çıkarın.

$((${#$(readlink -f /etc/fstab; echo .)} - 1))

İstediğiniz şey komutun çıktısındaki yük ise, o zaman burada iki tane çıkarmanız gerekir , çünkü çıktısı readlink -fkanonik yol artı bir yeni satırdır.

$((${#$(readlink -f /etc/fstab; echo .)} - 2))

Bu ${#$(readlink -f /etc/fstab)}, kanonik yolun kendisinin bir satırsonu ile bittiği nadir ancak olası durumdan farklıdır .

Bu özel örnek için, harici bir yardımcı programa ihtiyacınız yoktur, zsh readlink -fgeçmiş değiştirici aracılığıyla eşdeğer bir yerleşik yapıya sahiptir A.

echo /etc/fstab(:A)

Uzunluğu almak için parametre genişleticide geçmiş değiştiriciyi kullanın:

${#${:-/etc/fstab}:A}

Bir değişkente dosya adı varsa filename, bu olur ${#filename:A}.

Bourne / POSIX tarzı mermiler

Saf Bourne / POSIX mermilerinin hiçbirinde (Bourne, ash, mksh, ksh93, bash, yash…) bildiğim benzer bir uzantı yok. Komut ikamesinin çıkışına parametre ikamesi uygulamanız veya parametre ikamelerini iç içe yerleştirmeniz gerekiyorsa, ardışık aşamaları kullanın.

İsterseniz işlemi bir işleve doldurabilirsiniz.

command_output_length_sans_trailing_newlines () {
  set -- "$("$@")"
  echo "${#1}"
}

veya

command_output_length () {
  set -- "$("$@"; echo .)"
  echo "$((${#1} - 1))"
}

ancak genellikle faydası yoktur; ksh93 dışında, fonksiyonun çıktısını kullanabilmek için ekstra bir çatalın kullanılmasına neden olur, bu yüzden komut dosyanızı yavaşlatır ve nadiren okunabilirlik avantajı vardır.

Bir kez daha, readlink -fkanonik yol artı bir yeni satır çıktısıdır ; kanonik yolun uzunluğunu istiyorsanız, 1 inç yerine 2 çıkarın command_output_length. Kullanmak command_output_length_sans_trailing_newlinesdoğru sonucu ancak kurallı yolun kendisi bir satırsonu ile bitmediğinde verir.

Baytlar ve Karakterler

${#…}çok baytlı yerlerde fark yaratan, bayt cinsinden değil karakter cinsinden uzunluk olması gerekiyordu. Ksh93, bash ve zsh'nin makul güncel sürümleri LC_CTYPE, ${#…}yapının genişletildiği zamandaki değere göre karakterlerin uzunluğunu hesaplar . Diğer birçok yaygın kabuk gerçekten çokbaytlı yerel ayarları desteklemez: tire 0.5.7, mksh 46 ve posh 0.12.3'ten itibaren, ${#…}uzunluğu bayt cinsinden döndürür. Karakterlerin uzunluğunu güvenilir bir şekilde istiyorsanız, wcyardımcı programı kullanın :

$(readlink -f /etc/fstab | wc -m)

$LC_CTYPEGeçerli bir yerel ayar belirttiği sürece , bunun hata vereceğinden (çok baytlı yerel ayarları desteklemeyen eski veya kısıtlı bir platformda) veya karakterlerde doğru uzunluğu döndüreceğinden emin olabilirsiniz. (Unicode için, “karakter cinsinden uzunluk”, kod noktası sayısı anlamına gelir - karakterleri birleştirme gibi komplikasyonlar nedeniyle glif sayısı başka bir hikaye.)

Uzunluğu bayt cinsinden istiyorsanız, LC_CTYPE=Cgeçici olarak ayarlayın veya wc -cyerine kullanın wc -m.

İle bayt veya karakter sayma wc, komuttaki sondaki yeni satırları içerir. Kanonik yolun uzunluğunu bayt cinsinden istiyorsanız,

$(($(readlink -f /etc/fstab | wc -c) - 1))

Karakterlerle elde etmek için 2 çıkarın.


@cuonglm Hayır, 1 çıkarmanız gerekir. echo .İki karakter ekler, ancak ikinci karakter, komut ikamesi ile soyulmuş bir son satırdır .
Gilles 'SO- kötü olmayı kes

Yeni satır readlinkçıktıdan, artı .by echo. İkimiz de echo .iki karakter eklemeye katılıyoruz ancak sondaki yeni satır çıkarıldı. Yanıtımı unix.stackexchange.com/a/160499/38906 ile deneyin printf .veya görün .
cuonglm

@cuonglm Soru, komutun çıktısındaki karakter sayısını sordu. Çıkışı readlinkbağlantı hedefi artı bir yeni satırdır.
Gilles 'SO- kötü olmayı kes

0

Bu işe yarıyor, dashancak hedeflenen var'ın kesinlikle boş veya ayarlanmamış olmasını gerektiriyor. Bu yüzden bu aslında iki komut - $lilkinde açıkça boş :

l=;printf '%.slen is %d and result is %s\n' \
    "${l:=$(readlink -f /etc/fstab)}" "${#l}" "$l"

ÇIKTI

len is 10 and result is /etc/fstab

Tabii ki değil, tüm kabuk yapıları budur, readlinkancak mevcut kabukta bu şekilde değerlendirilirken, %.slen'i almadan önce ödevi yapmanız gerektiği anlamına gelir, bu yüzden printfbiçim dizesindeki ilk argümanı susturup tekrar eklemek için printfarg listesinin kuyruğundaki değişmez değer .

İle eval:

l=$(readlink -f /etc/fstab) eval 'l=${#l}:$l'
printf %s\\n "$l"

ÇIKTI

10:/etc/fstab

Aynı şeye yaklaşabilirsiniz, ancak ilk komuttaki bir değişkenin çıktısı yerine stdout'ta alabilirsiniz:

PS4='${#0}:$0' dash -cx '2>&1' "$(readlink -f /etc/fstab)"

... yazıyor ...

10:/etc/fstab

... geçerli kabuktaki herhangi bir değişkene herhangi bir değer atamadan dosya tanımlayıcı 1'e.


1
OP'nin kaçınmak istediği tam olarak bu değil miydi? "Çıktıyı bir değişkene kaydederek bunu yapabilmenin mümkün olduğunu anlıyorum: variable=$(readlink -f /etc/fstab); echo ${#variable};Ama fazladan adımı kaldırmak istiyorum."
terdon

@terdon, muhtemelen yanlış anladım, ancak noktalı virgülün değişken değil problem olduğu izlenimimdi. Bu yüzden bunlar len ve çıktıyı sadece kabuk yerleşiklerini kullanarak tek bir basit komutla alır. Kabuk, önce readlink sonra exec yürütmez expr. Muhtemelen sadece len'in değeri bir şekilde alması, bunun neden olabileceğini anlamakta zorluk çektiğimi itiraf etmeliyim, ancak bunun önemli bir durum olabileceğinden şüpheleniyorum.
mikeserv

1
Bu evalarada, bu arada, muhtemelen en temiz olanıdır - çıktıyı ve len'i tek bir yürütmede aynı var isme atar - yapmaya çok yakın l=length(l):out(l). Doing expr length $(command) yapar bu arada, len lehine değerini tıkamaktadır.
mikeserv
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.