Bir dize değişkenindeki satır sayısını POSIX-ly saymak nasıl?


10

Bunu Bash'de yapabileceğimi biliyorum:

wc -l <<< "${string_variable}"

Temel olarak, bulduğum her şey <<<Bash operatörünü içeriyordu .

Ama POSIX kabuğunda <<<tanımsız ve saatlerdir alternatif bir yaklaşım bulamadım. Bunun basit bir çözümü olduğundan eminim, ama ne yazık ki şimdiye kadar bulamadım.

Yanıtlar:


11

Bunun basit cevabı wc -l <<< "${string_variable}"ksh / bash / zsh kısayoludur printf "%s\n" "${string_variable}" | wc -l.

Aslında <<<ve bir boru işçiliğinde farklılıklar vardır : <<<komuta girdi olarak geçirilen geçici bir dosya |oluştururken bir boru oluşturur. Bash ve pdksh / mksh'de (ancak ksh93 veya zsh'da değil), borunun sağ tarafındaki komut bir alt kabukta çalışır. Fakat bu farklılıklar bu özel durumda önemli değil.

Satır sayma açısından, değişkenin boş olmadığını ve bir satırsonu ile bitmediğini varsayar. Değişkenin bir komut ikamesinin sonucu olduğu bir yeni satırla bitmemesi durumudur, bu nedenle çoğu durumda doğru sonucu alırsınız, ancak boş dize için 1 alırsınız.

İki arasındaki farklar var=$(somecommand); wc -l <<<"$var"ve somecommand | wc -l: Komut değişimini kullanarak ve geçici bir değişken uzakta sonunda boş satırlar, çıktının son satırında yeni bir satır ile sona erdi olsun ya da olmasın unutur şeritler (komut geçerli bir boş olmayan metin dosyasını çıktılar her zaman yapar) ve çıktı boşsa bir sayılır. Hem sonucu korumak hem de satırları saymak istiyorsanız, bilinen bazı metinleri ekleyip sonunda sıyırıp yapabilirsiniz:

output=$(somecommand; echo .)
line_count=$(($(printf "%s\n" "$output" | wc -l) - 1))
printf "The exact output is:\n%s" "${output%.}"

1
@Inian Keep wc -l, orijinaline tam olarak eşdeğerdir: <<<$foodeğerine yeni bir satır ekler $foo( $fooboş olsa bile ). Cevabımda bunun neden istenmediğini açıklıyorum, ama sorulan şey bu.
Gilles 'SO- kötü olmayı bırak'

2

Gibi ek programlar kullanılarak, dahili ins kabuk uygun değil grepve awkPOSIX uyumlu seçenekleriyle,

string_variable="one
two
three
four"

İle yaparak grepçizgilerin başlangıç eşleşecek

printf '%s' "${string_variable}" | grep -c '^'
4

Ve birlikte awk

printf '%s' "${string_variable}" | awk 'BEGIN { count=0 } NF { count++ } END { print count }'

Bazı GNU araçlarının, özellikle GNU'nun , aracın POSIX sürümünü çalıştırma seçeneğine grepuymadığını unutmayın POSIXLY_CORRECT=1. Gelen grepdeğişkeni ayarlayarak etkilenen tek davranış komut satırı bayrakları düzenin işlenmesinde fark olacaktır. Dokümantasyondan (GNU grepkılavuzu), öyle görünüyor ki

POSIXLY_CORRECT

Ayarlanırsa, grep POSIX'in gerektirdiği şekilde davranır; aksi takdirde grepdiğer GNU programlarına benzer şekilde davranır. POSIX, dosya adlarını izleyen seçeneklerin dosya adı olarak ele alınmasını gerektirir; varsayılan olarak, bu seçeneklere işlenen listesinin önüne izin verilir ve seçenek olarak kabul edilir.

Bakınız grep'te POSIXLY_CORRECT nasıl kullanılır?


2
Kesinlikle wc -lburada hala geçerli mi?
Michael Homer

@MichaelHomer: Gözlemlediğim kadarıyla, wc -luygun bir yeni satırla sınırlandırılmış akışa ihtiyaç duyuyor (sonunda doğru şekilde saymak için bir '\ n` sonuna sahip). Basit bir FIFO ile kullanmak için kullanılamaz printf, örneğin printf '%s' "${string_variable}" | wc -lbeklendiği gibi çalışmayabilir, ancak testlerin eklediği <<<sondaki iz yüzünden \nolabilir
Inian

1
printf '%s\n'Siz çıkarmadan önce olan buydu ...
Michael Homer

1

Here-string <<<, burada-belgenin hemen hemen tek satırlık bir versiyonudur <<. Birincisi standart bir özellik değil, ikincisi. <<Bu durumda da kullanabilirsiniz . Bunlar eşdeğer olmalıdır:

wc -l <<< "$somevar"

wc -l << EOF
$somevar
EOF

Değişkenin yalnızca beş satırı olmasına rağmen, her ikisinin de ek bir satırsonu eklemesine dikkat edin $somevar, örneğin bu baskılar 6:

s=$'foo\n\n\nbar\n\n'
wc -l <<< "$s"

İle printfek satırsonu isteyip istemediğinize karar verebilirsiniz:

printf "%s\n" "$s" | wc -l         # 6
printf "%s"   "$s" | wc -l         # 5

Ancak, wcyalnızca tam satırları (veya dizedeki yeni satır karakteri sayısını) sayar. grep -c ^son satır parçasını da saymalıdır.

s='foo'
printf "%s" "$s" | wc -l           # 0 !

printf "%s" "$s" | grep -c ^       # 1

(Tabii ki ${var%...}, bir kerede birer birer kaldırmak için genişletmeyi kullanarak tamamen kabuktaki çizgileri sayabilirsiniz ...)


0

Gerçekten yapmanız gereken, değişken içindeki tüm boş olmayan çizgileri bir şekilde (saymak da dahil olmak üzere) işlemek olan şaşırtıcı derecede sık olan durumlarda , IFS'yi sadece yeni bir satıra ayarlayabilir ve ardından kabuğun kelime ayırma mekanizmasını kırmak için kullanabilirsiniz. boş olmayan çizgiler birbirinden ayrılır.

Örneğin, sağlanan tüm bağımsız değişkenlerin içindeki boş olmayan satırları toplayan küçük bir kabuk işlevi:

lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)

Burada parantez yerine parantezler, işlev gövdesi için bileşik komutu oluşturmak üzere kullanılır. Bu, işlevin bir alt kabukta yürütülmesini sağlar, böylece her çağrıda dış dünyanın IFS değişkenini ve yol adı genişletme ayarını kirletmez.

Boş olmayan satırlar üzerinden yineleme yapmak istiyorsanız, bunu benzer şekilde yapabilirsiniz:

IFS='
'
set -f
for line in $lines
do
    printf '[%s]\n' $line
done

IFS'yi bu şekilde değiştirmek, genellikle gözden kaçan bir tekniktir ve sekmeyle ayrılmış sütunsal girdiden boşluklar içerebilecek yol adlarını ayrıştırmak gibi şeyler yapmak için de kullanışlıdır. Bununla birlikte, IFS'nin space-tab-newline'ın varsayılan ayarında yer alan boşluk karakterini kasıtlı olarak kaldırmanın, normalde görmeyi beklediğiniz yerlerde kelime bölünmesini devre dışı bırakabileceğini bilmeniz gerekir.

Örneğin, değişkenler için karmaşık bir komut satırı oluşturmak üzere değişkenler kullanıyorsanız ffmpeg, -vf scale=$scaleyalnızca değişken scaleboş olmayan bir şeye ayarlandığında dahil etmek isteyebilirsiniz . Normalde bunu ile başarabilirsiniz, ${scale:+-vf scale=$scale}ancak IFS, bu parametre genişletme tamamlandığında normal boşluk karakterini içermiyorsa, arasındaki -vfve scale=bir kelime ayırıcısı olarak kullanılmayacak ve ffmpeghepsi -vf scale=$scaletek bir argüman olarak geçirilecektir , ki anlamayacak.

Bu sorunu gidermek için, emin IFS yapmadan önce daha normal kuruldu yapmak ya ihtiyacı olur ${scale}genişleme ya da iki genişlemeleri yapın: ${scale:+-vf} ${scale:+scale=$scale}. Kabuğun komut satırlarını ilk ayrıştırma sürecinde yaptığı bölme sözcüğü, bu komut satırlarını işlemenin genişletme aşamasında yaptığı bölme yerine, IFS'ye bağlı değildir.

Bu tür bir şey yapacaksanız, sadece bir sekmeyi ve sadece bir satırsonu tutmak için iki küresel kabuk değişkeni yaratmak olacaktır:

t=' '
n='
'

Bu şekilde , tüm kodunuzu tırnak işaretli boşlukla doldurmak yerine sekmelere ve yeni satırlara ihtiyacınız olan genişletmeleri dahil edebilir $tve ekleyebilirsiniz $n. Bunu yapmak için başka bir mekanizmaya sahip olmayan bir POSIX kabuğunda alıntılanmış boşluktan tamamen kaçınmayı tercih ederseniz printf, komut genişletmelerinde son satırların kaldırılması için biraz uğraşmanız gerekmesine rağmen yardımcı olabilir:

nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}

Bazen IFS'yi komut başına ortam değişkeni gibi ayarlamak iyi çalışır. Örneğin, sekmeyle ayrılmış giriş dosyasının her satırından boşluk ve ölçeklendirme faktörü içermesine izin verilen bir yol adını okuyan bir döngü:

while IFS=$t read -r path scale
do
    ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt

Bu durumda, readyerleşik IFS'nin sadece bir sekmeye ayarlandığını görür, böylece okuduğu giriş satırını boşluklarda da bölmez. Ama IFS=$t set -- $lines değil işi: kabuk genişlediğinde $linesbunun yanı inşa setbuiltin en argümanlar önce kendisi yerleşik yürütülürken sadece geçerli bir şekilde IFS geçici ayarı çok geç gelir, böylece komutu çalıştırılıyor. Bu yüzden yukarıda verdiğim kod parçacıkları IFS'yi ayrı bir adımda ayarladılar ve bu nedenle koruma sorunu ile uğraşmak zorundalar.

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.