shell: komut yerine yeni satırlar ('\ n') koy


14

Sondaki yeni satır karakterleri de dahil olmak üzere, bir komut ikamesinin kesin çıktısını yakalayabilmek istiyorum .

Varsayılan olarak soyulduklarının farkındayım, bu yüzden onları tutmak için bazı manipülasyonlar gerekebilir ve orijinal çıkış kodunu korumak istiyorum .

Örneğin, değişken sayıda son satır ve çıkış kodu içeren bir komut verildiğinde:

f(){ for i in $(seq "$((RANDOM % 3))"); do echo; done; return $((RANDOM % 256));}
export -f f

Gibi bir şey çalıştırmak istiyorum:

exact_output f

Ve çıktı olması:

Output: $'\n\n'
Exit: 5

Hem bashPOSIX ile ilgileniyorum sh.


1
Newline bir parçasıdır $IFS, bu yüzden argüman olarak ele alınmayacaktır.
Deathgrip

4
@Deathgrip Bunun bir ilgisi yok IFS(deneyin ( IFS=:; subst=$(printf 'x\n\n\n'); printf '%s' "$subst" ). Sadece yeni satırlar çıkarılır \tve `` IFSetkilemez ve etkilemez.
PSkocik


Ayrıca bakınız: tcshtcsh
Stéphane Chazelas

Yanıtlar:


17

POSIX mermileri

Bir komutun tüm stdout'unu almak için olağan ( 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ) hile yapmak:

output=$(cmd; ret=$?; echo .; exit "$ret")
ret=$?
output=${output%.}

Fikir eklemek ve ekstra .\n. Komut ikamesi sadece şerit olacak o \n . Ve şerit .ile ${output%.}.

zshÇıkışı NUL bayt varsa, bunun dışındaki kabuklarda yine de çalışmayacağını unutmayın. İle yash, çıktı metin değilse bu çalışmaz.

Ayrıca, bazı yerlerde, sonuna eklemek için hangi karakteri kullandığınızın önemli olduğunu unutmayın. .genel olarak iyi olmalı, ancak bazıları olmayabilir. Örneğin x(diğer bazı cevaplarda kullanıldığı gibi) veya @BIG5, GB18030 veya BIG5HKSCS karakter kümelerini kullanan bir yerel ayarda çalışmaz. Bu karakter kümelerinde, birkaç karakterin kodlaması veya (0x78, 0x40) kodlamasıyla aynı baytla biterx@

Örneğin, ūBIG5HKSCS'de 0x88 0x78'dir (ve ASCII'de olduğu gibi 0x78'dir x, bir sistemdeki tüm karakter kümelerinin, İngilizce karakterleri içeren taşınabilir karakter kümesinin tüm karakterleri için aynı kodlamaya sahip olması gerekir @ve .). Yani eğer cmdoldu printf '\x88've biz takılı xondan sonra, ${output%x}o şerit başarısız olur xolarak $outputaslında içerecektir ū.

.Kodlama ile aynı kodlamada biten herhangi bir karakter varsa, bunun yerine kullanmak teoride aynı soruna yol açabilir ., ancak bir süre önce kontrol ettikleri için, bir yerel ayarda kullanılabilecek karakter kümelerinin hiçbirinin Debian, FreeBSD veya Solaris sistemleri benim için yeterince iyi karakterlere sahip (ve neden yerleştiğim ., aynı zamanda İngilizcede bir cümlenin sonunu işaretleme sembolü de uygun görünüyor).

@Arrow tarafından ele alındığı gibi daha doğru bir yaklaşım, yalnızca son bir karakterin ( ${output%.}) soyulması için yerel ayarın C olarak değiştirilmesi olacaktır; Kendi.

bash / zsh alternatifleri

İle bashve zshçıkış hiçbir NULs vardır varsayarak, ayrıca yapabilirsiniz:

IFS= read -rd '' output < <(cmd)

Çıkış durumunu almak için cmdbunu yapabilirsiniz wait "$!"; ret=$?içinde bashdeğil de zsh.

rc / es / akanaga

Tamlık için, rc/ es/ bunun akangaiçin bir operatör olduğunu unutmayın. Bunlarda, `cmd(veya `{cmd}daha karmaşık komutlar için) olarak ifade edilen komut ikamesi bir liste döndürür ( $ifsvarsayılan olarak boşluk-sekme-yeni satırını bölerek ). Bu mermilerde (Bourne benzeri mermilerin aksine), yeni satırın soyulması sadece bu $ifsbölünmenin bir parçası olarak yapılır . Böylece ayırıcıları $ifsbelirlediğiniz ``(seps){cmd}formu boşaltabilir veya kullanabilirsiniz :

ifs = ''; output = `cmd

veya:

output = ``()cmd

Her durumda, komutun çıkış durumu kaybolur. Çıktıya gömmeniz ve daha sonra çirkin hale gelmesi için çıkarmanız gerekir.

balık

Balıklarda, komut ikamesi vardır (cmd)ve alt kabuk içermez.

set var (cmd)

Boş değilse if $varçıktısındaki tüm satırları veya boşsa en fazla bir ( diğer tüm kabuklarda aksine) soyulmuş çıktısı olan bir dizi oluşturur .cmd$IFScmd$IFS

Bu yüzden hala bir sorun var (printf 'a\nb')ve (printf 'a\nb\n')boş olanlarla bile aynı şeye genişliyor $IFS.

Bu soruna geçici bir çözüm bulmak için, en iyi ben geldim:

function exact_output
  set -l IFS . # non-empty IFS
  set -l ret
  set -l lines (
    cmd
    set ret $status
    echo
  )
  set -g output ''
  set -l line
  test (count $lines) -le 1; or for line in $lines[1..-2]
    set output $output$line\n
  end
  set output $output$lines[-1]
  return $ret
end

Bir alternatif yapmak:

read -z output < (begin; cmd; set ret $status; end | psub)

Bourne kabuğu

Bourne kabuğu ne $(...)formu ne de ${var%pattern}operatörü desteklemedi, bu yüzden orada ulaşmak oldukça zor olabilir. Bir yaklaşım eval ve alıntı kullanmaktır:

eval "
  output='`
    exec 4>&1
    ret=\`
      exec 3>&1 >&4 4>&-
      (cmd 3>&-; echo \"\$?\" >&3; printf \"'\") |
        awk 3>&- -v RS=\\\\' -v ORS= -v b='\\\\\\\\' '
          NR > 1 {print RS b RS RS}; {print}; END {print RS}'
    \`
    echo \";ret=\$ret\"
  `"

Burada bir

output='output of cmd
with the single quotes escaped as '\''
';ret=X

geçilecek eval. POSIX yaklaşımına gelince ', kodlaması diğer karakterlerin sonunda bulunan bu karakterlerden biriyse, bir sorunumuz olurdu (komut enjeksiyon güvenlik açığı olacağı için çok daha kötü bir sorun), ama neyse ki ., bunlardan biri değildir ve bu alıntı tekniği genellikle kabuk kodunu tırnak içine alan herhangi bir şey tarafından kullanılan tekniktir ( \sorunu olduğunu unutmayın , bu nedenle kullanılmamalıdır (ayrıca "..."bazı karakterler için ters eğik çizgi kullanmanız gerekenleri de hariç tutar ) Burada, sadece bir sorundan sonra kullanıyoruz ').

tcsh

Komut ikamesinde tcsh koruma satırlarını görün `...`

(geçici bir dosyaya kaydederek ( echo $status > $tempfile:qkomuttan sonra) adresleyebileceğiniz çıkış durumuna dikkat etmemek )


Teşekkürler - ve özellikle farklı karakter setlerindeki ipucu için. Eğer zshsaklayabilirsiniz NULbir değişkene neden olmaz IFS= read -rd '' output < <(cmd)çalışacak? Bir dizenin uzunluğunu saklayabilmesi gerekir ... 0 baytlık bir dize yerine ''1 baytlık bir dize olarak \0kodlanır mı?
Tom Hale

1
@TomHale, evet, read -d ''olarak kabul edilir read -d $'\0'(içinde bashde var gerçi $'\0'aynıdır ''her yerde).
Stéphane Chazelas

Karakterleri ve baytları karıştırıyorsunuz. Eklenenleri tam olarak kaldırırsak orijinal varlığın değişmemesi gerektiğini lütfen unutmayın. Bu zor olması kaldırmak değil mi bir bayt olarak adlandırılan xbu ilave edilmiştir şey ise. Lütfen düzenlenen cevabıma bir göz atın.
Isaac

@Arrow, evet var=value command evalhile burada ( ayrıca ) ve austin grubu posta listesinde daha önce tartışıldı . Taşınabilir olmadığını göreceksiniz (ve bunun gibi a=1 command eval 'unset a; a=2'kullanılması gerekmediği veya daha kötüsü denediğinizde oldukça açıktır ). Aynı başlangıçta unset savedVAR=$VAR;...;VAR=$savedVARzaman istediğinizi yapmaz $VAR. Bu sadece teorik bir soruna (pratikte vurulamayacak bir hata), IMO'ya çalışmaksa, zahmete değmez. Yine de seni denediğin için destekleyeceğim.
Stéphane Chazelas

LANG=CBir dizeden bir bayt kaldırmak için kullandığınız ve son olarak attığınız bir bağlantınız var mı? Gerçek nokta etrafında endişeler dile getiriyorsun, hepsini çözmek kolay. (1) kullanılmamış ayar yoktur (2) Değişkeni değiştirmeden önce test edin. @ StéphaneChazelas
Isaac

3

Yeni soru için bu komut dosyası çalışır:

#!/bin/bash

f()           { for i in $(seq "$((RANDOM % 3 ))"); do
                    echo;
                done; return $((RANDOM % 256));
              }

exact_output(){ out=$( $1; ret=$?; echo x; exit "$ret" );
                unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL
                LC_ALL=C ; out=${out%x};
                unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL
                 printf 'Output:%10q\nExit :%2s\n' "${out}" "$?"
               }

exact_output f
echo Done

Yürütme sırasında:

Output:$'\n\n\n'
Exit :25
Done

Daha uzun açıklama

POSIX mermilerinin çıkarılmasıyla ilgili olağan bilgelik \n:

ekle x

s=$(printf "%s" "${1}x"); s=${s%?}

Son yeni hattı (çünkü gerekli olan S ) başına komut genişlemesi ile çıkarılır POSIX tarifname :

yer değiştirmenin sonunda bir veya daha fazla karakterin dizilerinin kaldırılması.


Bir iz hakkında x.

Bu soruda, bir xkodlamanın bazı karakterlerin sondaki baytı ile karıştırılabileceği söylenmiştir . Ancak, bazı dillerde kodlamanın hangi dilde veya hangi karakterin daha iyi olduğunu nasıl tahmin edeceğiz, en azından söylemek gerekirse, bu zor bir öneri.

Ancak; Bu sadece yanlış .

İzlememiz gereken tek kural, tam olarak kaldırdığımızı eklemektir .

Mevcut bir dizeye (veya bayt dizisine) bir şey eklersek ve daha sonra tamamen aynı şeyi kaldırırsak , orijinal dizenin (veya bayt dizisinin) aynı olması gerektiğini anlamak kolay olmalıdır .

Nerede yanlış gidiyoruz? Biz ne zaman karıştırmak karakterleri ve bayt .

Bir bayt eklersek, bir baytı kaldırmalıyız, bir karakter eklersek aynı karakteri kaldırmamız gerekir .

İkinci seçenek, bir karakter eklemek (ve daha sonra aynı karakteri kaldırmak) kıvrık ve karmaşık hale gelebilir ve evet, kod sayfaları ve kodlamalar engel olabilir.

Bununla birlikte, ilk seçenek oldukça mümkündür ve açıkladıktan sonra basit hale gelecektir.

Bir bayt, bir ASCII baytı (<127) ekleyelim ve işleri mümkün olduğunca daha az kıvrımlı tutmak için az aralığında ASCII karakteri olduğunu varsayalım. Ya da söylememiz gerektiği gibi, onaltılık aralıktaki bir bayt 0x61- 0x7a. Bunlardan herhangi birini seçelim, belki bir x (gerçekten bir değer bayt 0x78). Böyle bir baytı bir dizeye x ile birleştirerek ekleyebiliriz (varsayalım é):

$ a
$ b=${a}x

Dizeye bayt dizisi olarak bakarsak, şunu görürüz:

$ printf '%s' "$b" | od -vAn -tx1c
  c3  a9  78
 303 251   x

X ile biten dize sırası.

Bu x'i (bayt değeri 0x78) kaldırırsak :

$ printf '%s' "${b%x}" | od -vAn -tx1c
  c3  a9
 303 251

Sorunsuz çalışır.

Biraz daha zor bir örnek.

İlgilendiğimiz dize bayt ile biter 0xc3:

$ a=$'\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x20\xc3'

Ve bir bayt değer ekleyelim 0xa9

$ b=$a$'\xa9'

Dize şimdi bu oldu:

$ echo "$b"
a test string é

Tam olarak istediğim gibi, son iki bayt utf8'de bir karakterdir (böylece herkes bu sonuçları utf8 konsolunda çoğaltabilir).

Bir karakteri kaldırırsak, orijinal dize değiştirilir. Ama eklediğimiz bu değil, x olarak yazılan bir bayt değeri ekledik, ama yine de bir bayt.

Baytları karakter olarak yanlış yorumlamaktan kaçınmamız gerekenler. İhtiyacımız olan, kullandığımız baytı kaldıran bir eylem 0xa9. Aslında, kül, bash, lksh ve mksh tam olarak bunu yapıyor gibi görünüyor:

$ c=$'\xa9'
$ echo ${b%$c} | od -vAn -tx1c
 61  20  74  65  73  74  20  73  74  72  69  6e  67  20  c3  0a
  a       t   e   s   t       s   t   r   i   n   g     303  \n

Ama ksh veya zsh değil.

Bununla birlikte, bunu çözmek çok kolaydır, tüm bu kabuklara bayt kaldırma işlemini söyleyelim :

$ LC_ALL=C; echo ${b%$c} | od -vAn -tx1c 

işte bu, test edilen tüm mermiler (yash hariç) (ipin son kısmı için):

ash             :    s   t   r   i   n   g     303  \n
dash            :    s   t   r   i   n   g     303  \n
zsh/sh          :    s   t   r   i   n   g     303  \n
b203sh          :    s   t   r   i   n   g     303  \n
b204sh          :    s   t   r   i   n   g     303  \n
b205sh          :    s   t   r   i   n   g     303  \n
b30sh           :    s   t   r   i   n   g     303  \n
b32sh           :    s   t   r   i   n   g     303  \n
b41sh           :    s   t   r   i   n   g     303  \n
b42sh           :    s   t   r   i   n   g     303  \n
b43sh           :    s   t   r   i   n   g     303  \n
b44sh           :    s   t   r   i   n   g     303  \n
lksh            :    s   t   r   i   n   g     303  \n
mksh            :    s   t   r   i   n   g     303  \n
ksh93           :    s   t   r   i   n   g     303  \n
attsh           :    s   t   r   i   n   g     303  \n
zsh/ksh         :    s   t   r   i   n   g     303  \n
zsh             :    s   t   r   i   n   g     303  \n

Sadece bu kadar basit, tüm bayt değerleri için tam olarak bir bayt olan bir LC_ALL = C karakterini kaldırmak için kabuk anlatmak 0x00için 0xff.

Yorumlar için çözüm:

Yorumlarda tartışılan örnek için, (zsh'de başarısız olan) olası bir çözüm:

#!/bin/bash

LC_ALL=zh_HK.big5hkscs

a=$(printf '\210\170');
b=$(printf '\170');

unset OldLC_ALL ; [ "${LC_ALL+set}" ] && OldLC_ALL=$LC_ALL
LC_ALL=C ; a=${a%"$b"};
unset LC_ALL ; [ "${OldLC_ALL+set}" ] && LC_ALL=$OldLC_ALL

printf '%s' "$a" | od -vAn -c

Bu kodlama sorununu ortadan kaldıracaktır.


Birden fazla sondaki yeni satırın kaldırılabileceğini bilmek güzel.
Tom Hale


${var%?}Teoride teorik olarak her zaman bir bayt çizgisinin daha doğru olduğundan emin olmak için yerel ayarı C'ye sabitlemenin kabul edildiğini kabul ediyorum , ancak: 1- LC_ALLve LC_CTYPEgeçersiz kılın $LANG, bu nedenle LC_ALL=C2- var=${var%?}'yi ayarlamanız gerekir. kaybolur, bu yüzden değerini ve durumunu kaydetmeniz ve geri yüklemeniz gerekir LC_ALL(veya POSIX dışı localkapsam özelliklerine başvurmanız gerekir ) 3- yerel ayarın kodun ortasında değiştirilmesi yash gibi bazı kabuklarda tam olarak desteklenmez. Öte yandan, pratikte .gerçek yaşam karakter kümelerinde hiçbir zaman sorun olmaz, bu yüzden kullanmak LC_ALL ile karışmayı önler.
Stéphane Chazelas

2

Normal çıktıdan sonra bir karakter çıktısı alabilir ve daha sonra şeritleyebilirsiniz:

#capture the output of "$@" (arguments run as a command)
#into the exact_output` variable
exact_output() 
{
    exact_output=$( "$@" && printf X ) && 
    exact_output=${exact_output%X}
}

Bu POSIX uyumlu bir çözümdür.


Yanıtlara dayanarak sorumun belirsiz olduğunu görüyorum. Sadece güncelledim.
Tom Hale
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.