Bash kullanarak son komutun çıktısını otomatik olarak bir değişkene kaydedilsin mi?


139

Sonraki komutta son yürütülen komutun sonucunu kullanabilmek istiyorum. Örneğin,

$ find . -name foo.txt
./home/user/some/directory/foo.txt

Şimdi dosyayı bir düzenleyicide açabileceğimi veya silebildiğimi veya onunla başka bir şey yapabileceğimi varsayalım, ör.

mv <some-variable-that-contains-the-result> /some/new/location

Nasıl yapabilirim? Belki bash değişkeni mi kullanıyorsunuz?

Güncelleme:

Açıklığa kavuşturmak için, işleri manuel olarak atamak istemiyorum. Neyin peşindeyim yerleşik bash değişkenleri gibi bir şey.

ls /tmp
cd $_

$_önceki komutun son argümanını tutar. Benzer bir şey istiyorum, ama son komutun çıktısıyla.

Son güncelleme:

Seth'in cevabı oldukça iyi çalıştı. Akılda tutulması gereken birkaç şey:

  • touch /tmp/xÇözümü ilk kez denerken unutmayın
  • sonuç yalnızca son komutun çıkış kodu başarılı olduğunda saklanır

Düzenlemenizi gördükten sonra cevabımı silmeyi düşündüm. Aradığın yerleşik bir şey olup olmadığını merak ediyorum.
taskinoor

Yerleşik bir şey bulamadım. Bunu uygulamak mümkün olup olmadığını merak ediyordum .. belki .bahsrc aracılığıyla? Bence oldukça kullanışlı bir özellik.
armandino

Korkarım, tek yapmanız gereken çıktıyı dosyaya veya boruya yönlendirmek veya yakalamaktır, aksi takdirde kaydedilmez.
bandi

3
Kabuk ve terminal işbirliği olmadan bunu yapamazsınız ve genellikle işbirliği yapmazlar. Ayrıca bkz . Komut satırından son çıktıyı nasıl yeniden kullanabilirim? ve önceki komutları çıkışından metin kullanma üzerine Unix Stack Borsası .
Gilles 'SO- kötü olmayı bırak'

6
Komutların çıktısının yakalanmamasının ana nedenlerinden biri, çıktının keyfi olarak büyük olması - bir seferde birçok megabayt olmasıdır. Verilen, her zaman o kadar büyük değil, ama büyük çıktılar sorun yaratıyor.
Jonathan Leffler

Yanıtlar:


71

Bu gerçekten acayip bir çözüm, ancak çoğu zaman işe yarıyor gibi görünüyor. Test sırasında ^C, komut satırına girerken bazen çok iyi çalışmadığını fark ettim, ancak biraz daha iyi davranmak için biraz ayarladım.

Bu kesmek sadece interaktif bir mod kesmek ve ben kimseye tavsiye etmem oldukça eminim. Arka plan komutlarının normalden daha az tanımlanmış davranışa neden olması muhtemeldir. Diğer cevaplar programlı olarak sonuçlara ulaşmanın daha iyi bir yoludur.


Olduğu söyleniyor, işte "çözüm":

PROMPT_COMMAND='LAST="`cat /tmp/x`"; exec >/dev/tty; exec > >(tee /tmp/x)'

Bu bash çevresel değişkenini ayarlayın ve komutları istediğiniz gibi yayınlayın. $LASTgenellikle aradığınız çıktıya sahip olacaktır:

startide seth> fortune
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve
startide seth> echo "$LAST"
Courtship to marriage, as a very witty prologue to a very dull play.
                -- William Congreve

1
@armandino: Bu, elbette, standart çıkıştaki bir terminalle etkileşime girmeyi bekleyen programların beklendiği gibi çalışmamasına (daha fazla / daha az) veya garip şeyleri $ LAST (emacs) içinde depolamasına neden olur. Ama bence alacağınız kadar iyi. Diğer tek seçenek, HER ŞEY'in bir kopyasını bir dosyaya kaydetmek için (type) komut dosyasını kullanmak ve son PROMPT_COMMAND'tan bu yana değişiklikleri kontrol etmek için PROMPT_COMMAND komutunu kullanmaktır. Bu, istemediğiniz şeyleri de içerecektir. Yine de, bunu istediğiniz şeye daha yakın bir şey bulamayacağınızdan eminim.
Seth Robertson

2
Biraz daha ileri gitmek: PROMPT_COMMAND='last="$(cat /tmp/last)";lasterr="$(cat /tmp/lasterr)"; exec >/dev/tty; exec > >(tee /tmp/last); exec 2>/dev/tty; exec 2> >(tee /tmp/lasterr)'hem $lastve sağlar $lasterr.
inkaphink

@cdosborn: adam varsayılan olarak çıktıyı bir çağrı cihazı aracılığıyla gönderir. Önceki yorumumun dediği gibi: "standart çıkıştaki bir terminalle etkileşime girmeyi bekleyen programlar beklendiği gibi çalışmıyor (az / çok)". daha fazla ve daha az çağrı cihazları.
Seth Robertson

Anladım:No such file or directory
Francisco Corrales Morales

@Raphink Sadece bir düzeltmeyi belirtmek istedim: 2> >(tee /tmp/lasterr 1>&2)standart çıktısının teetekrar standart hataya yönlendirilmesi gerektiğinden öneriniz sona ermelidir.
Hugues

90

Bunu yapan herhangi bir değişken bilmiyorum otomatik olarak . Sonucu kopyalayıp yapıştırma dışında bir şey yapmak için, az önce yaptığınız şeyi yeniden çalıştırabilirsiniz, ör.

vim $(!!)

!!Tarih genişlemesi 'önceki komut' nerede .

Düzgün argüman ayrışmasını engelleyebilecek boşluklar veya başka karakterler içeren tek bir dosya adı olmasını bekliyorsanız sonucu ( vim "$(!!)") belirtin . Boş olarak bırakılırsa, boşluk veya başka kabuk ayrıştırma belirteçleri içermediği sürece birden çok dosyanın aynı anda açılmasına izin verir.


1
Kopyalama yapıştırma genellikle böyle bir durumda yaptığım şeydir, çünkü genellikle komutun çıktısının yalnızca bir bölümüne ihtiyacınız vardır. İlk siz bahsettiğinize şaşırdım.
Bruno De Fraine

4
Aynı şeyi daha az tuş vim `!!`
vuruşuyla

Kabul edilen cevaptan farkı görmek için, infaz date "+%N"edip ardındanecho $(!!)
Marinos An

20

Bash biraz çirkin bir dildir. Evet, çıktıyı değişkene atayabilirsiniz

MY_VAR="$(find -name foo.txt)"
echo "$MY_VAR"

Ama umarım en zorun find bir sonuç döndüren ve bu sonucun, bir Bash değişkenine atandıklarında sessizce değiştirileceklerinden, satır başı veya satır beslemeleri gibi herhangi bir "tek" karakter içermediğini .

Ancak değişkeninizi kullanırken doğru bir şekilde alıntı yapmaya dikkat edin!

Doğrudan dosya üzerinde işlem yapmak daha iyidir, örneğin find's -execdir(el kitabına bakın).

find -name foo.txt -execdir vim '{}' ';'

veya

find -name foo.txt -execdir rename 's/\.txt$/.xml/' '{}' ';'

5
Onlar edilir değil atamak zaman sessizce ne zaman onlar değiştirilir, modifiye yankı! Sadece echo "${MY_VAR}"durumun bu olduğunu görmek için yapmanız gerekir.
paxdiablo

13

Bunu yapmanın birden fazla yolu vardır. Bunun bir yolu, v=$(command)komutun çıktısını atayacak olan kullanmaktır v. Örneğin:

v=$(date)
echo $v

Ve geri tırnak işaretleri de kullanabilirsiniz.

v=`date`
echo $v

Gönderen Bash Başlangıç Kılavuzu ,

Eski stil ters tırnaklı ikame biçimi kullanıldığında, ters eğik çizgi, "$", "" "veya" \ "ile başlayan durumlar dışında gerçek anlamını korur. Bir ters eğik çizgiden önce gelmeyen ilk geri tepmeler komut ikamesini sonlandırır. "$ (COMMAND)" formunu kullanırken, parantezler arasındaki tüm karakterler komutu oluşturur; hiçbirine özel muamele edilmez.

DÜZENLEME: Sorudaki düzenlemeden sonra, OP'nin aradığı şey bu değil gibi görünüyor. Bildiğim kadarıyla $_, son komutun çıktısı gibi özel bir değişken yok .


11

Oldukça kolay. Arka tırnak kullanın:

var=`find . -name foo.txt`

Ve sonra bunu gelecekte istediğiniz zaman kullanabilirsiniz

echo $var
mv $var /somewhere

11

Disclamers:

  • Bu cevap yarım yılın sonlarında: D
  • Ben ağır bir tmux kullanıcısıyım
  • Bunun çalışması için kabuğunuzu tmux'da çalıştırmalısınız.

Tmux'da etkileşimli bir kabuk çalıştırırken, bir terminalde görüntülenmekte olan verilere kolayca erişebilirsiniz. Bazı ilginç komutlara bakalım:

  • tmux yakalama bölmesi : görüntülenen verileri tmux'un dahili arabelleklerinden birine kopyalar. Şu anda görünmeyen geçmişi kopyalayabilir, ancak şimdi bununla ilgilenmiyoruz
  • tmux list-buffers : yakalanan arabellekler hakkındaki bilgileri görüntüler. En yeni olanın numarası 0 olacaktır.
  • tmux show-buffer -b (buffer num) : verilen arabellek içeriğini bir terminale yazdırır
  • tmux macunu-tampon -b (tampon numarası) : verilen tamponun içeriğini girdi olarak yapıştırır

Evet, bu bize şimdi birçok olasılık sunuyor :) Bana gelince, basit bir takma ad oluşturdum: alias L="tmux capture-pane; tmux showb -b 0 | tail -n 3 | head -n 1" ve şimdi son satıra her ihtiyacım olduğunda $(L)bunu elde etmek için kullanıyorum .

Bu, programın kullandığı çıkış akışından (stdin veya stderr olsun), yazdırma yönteminden (ncurses, vb.) Ve programın çıkış kodundan bağımsızdır - verilerin görüntülenmesi yeterlidir.


Hey, bu tmux ipucu için teşekkürler. Temelde OP ile aynı şeyi arıyordum, yorumunuzu buldum ve bahsettiğiniz tmux komutlarını ve Vim hareketlerini kullanarak önceki komutun bir kısmını seçip yapıştırmanıza izin vermek için bir kabuk komut dosyası kodladım
Bill Gribble

9

Ben kabuk içeren bir komut dosyasına kabuk içeren bir çözüm kesmek mümkün olabileceğini düşünüyorum:

#!/bin/sh
bash | tee /var/log/bash.out.log

Sonra $PROMPT_COMMANDbir sınırlayıcı çıktısını belirlerseniz _, o günlüğün son yığınını alan bir yardımcı işlev (belki de denir ) yazabilirsiniz , böylece şöyle kullanabilirsiniz:

% find lots*of*files
...
% echo "$(_)"
... # same output, but doesn't run the command again

7

Bash profilinizde aşağıdaki takma adı ayarlayabilirsiniz:

alias s='it=$($(history | tail -2 | head -1 | cut -d" " -f4-))'

Ardından, rasgele bir komuttan sonra 's' yazarak sonucu bir kabuk değişkenine 'it' kaydedebilirsiniz.

Yani örnek kullanım:

$ which python
/usr/bin/python
$ s
$ file $it
/usr/bin/python: symbolic link to `python2.6'

Teşekkür ederim! grabSon komuttan nth satırını panoya kopyalayan işlevimi
davidhq

6

Ben sadece bu bashişlevi burada önerilerden damıtılmış :

grab() {     
  grab=$("$@")
  echo $grab
}

Sonra, sadece şunları yaparsınız:

> grab date
Do 16. Feb 13:05:04 CET 2012
> echo $grab
Do 16. Feb 13:05:04 CET 2012

Güncelleme : yerine önerilen bir anonim kullanıcı echotarafından printf '%s\n'hangi gibi işlem seçeneklerini değil o avantajı -eyakaladı metinde. Bu nedenle, bu tür özellikleri bekliyorsanız veya deneyimliyorsanız, bu öneriyi düşünün. cat <<<$grabBunun yerine başka bir seçenek kullanmaktır .


1
Tamam, kesinlikle bu bir cevap değil, çünkü "otomatik" kısmı tamamen eksik.
Tilman Vogel

cat <<<$grabSeçeneği açıklayabilir misiniz ?
leoj

1
Alıntı man bash: "Here Strings: Buradaki belgelerin bir varyantı, format: <<<wordWord genişletilir ve standart girişindeki komuta verilir." Yani, değeri stdin'e $grabbeslenir catve catsadece stdout'a geri gönderir.
Tilman Vogel

5

"Son bir komutun sonucunu bir sonraki komutta kullanmak istiyorum" diyerek, sanırım - herhangi bir komutun sonucunu kastediyorsunuz, sadece bulmak değil.

Bu durumda - xargs aradığınız şey.

find . -name foo.txt -print0 | xargs -0 -I{} mv {} /some/new/location/{}

VEYA ilk önce çıktıyı görmek istiyorsanız:

find . -name foo.txt -print0

!! | xargs -0 -I{} mv {} /some/new/location/{}

Bu komut birden fazla dosyayla ilgilenir ve yol ve / veya dosya adı boşluklar içeriyor olsa bile bir cazibe gibi çalışır.

Uyarı mv {} / bazı / yeni / yer / {} komutunun parçası. Bu komut, önceki komut tarafından yazdırılan her satır için oluşturulur ve yürütülür. Burada {} yerine önceki komutla yazdırılan satır değiştirilir .

Xargs'ın man sayfasından alıntı:

xargs - standart girişten komut satırları oluşturma ve yürütme

Daha fazla bilgi için kılavuz sayfasına bakınız: man xargs


5

Çıkışı ters çentiklerle yakalayın:

output=`program arguments`
echo $output
emacs $output

4

Genellikle burada başkalarının önerdiklerini yaparım ... ödev olmadan:

$find . -iname '*.cpp' -print
./foo.cpp
./bar.cpp
$vi `!!`
2 files to edit

İsterseniz meraklısı alabilirsiniz:

$grep -R "some variable" * | grep -v tags
./foo/bar/xxx
./bar/foo/yyy
$vi `!!`

2

İstediğiniz tek şey son komutunuzu yeniden çalıştırmak ve çıktıyı almaksa, basit bir bash değişkeni işe yarayacaktır:

LAST=`!!`

Böylece komutunuzu çıktı üzerinde çalıştırabilirsiniz:

yourCommand $LAST

Bu yeni bir süreç oluşturacak ve komutunuzu yeniden çalıştıracak, sonra size çıktı verecektir. Gerçekten istediğiniz gibi komut çıktısı için bir bash geçmiş dosyası gibi geliyor. Bu, bash'ın terminalinize gönderdiği çıkışı yakalamanız gerektiği anlamına gelir. / Dev veya / proc'u izlemek için bir şeyler yazabilirsiniz, ama bu dağınık. Ayrıca, terim ve bash arasında, ortada bir çıkış komutunuza yönlendiren bir tee komutuyla "özel bir boru" oluşturabilirsiniz.

Ama her ikisi de bir tür hileli çözüm. Bence en iyi şey çıktı günlüğü ile daha modern bir terminal olan terminatör olurdu . Son komutun sonuçları için günlük dosyanızı kontrol etmeniz yeterlidir. Yukarıdakine benzer bir bash değişkeni bunu daha da basit hale getirecektir.


1

Komutunuzu yürüttükten ve sonucu bir değişkende saklamak istediğinize karar verdikten sonra bunu yapmanın bir yolu:

$ find . -name foo.txt
./home/user/some/directory/foo.txt
$ OUTPUT=`!!`
$ echo $OUTPUT
./home/user/some/directory/foo.txt
$ mv $OUTPUT somewhere/else/

Veya bir değişkenin sonucunu isteyeceğinizi önceden biliyorsanız, ters tırnakları kullanabilirsiniz:

$ OUTPUT=`find . -name foo.txt`
$ echo $OUTPUT
./home/user/some/directory/foo.txt

1

Mevcut yanıtlara alternatif olarak: whileDosya adlarınızda bu gibi boşluklar varsa kullanın :

find . -name foo.txt | while IFS= read -r var; do
  echo "$var"
done

Yazdığım gibi, fark sadece dosya adlarında boşluk beklemeniz gerekiyorsa geçerlidir.

Not: Sadece yerleşik olan şeyler çıktıyla değil, son komutun durumuyla ilgilidir.


1

kullanabilirsiniz !!: 1. Misal:

~]$ ls *.~
class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 

~]$ rm !!:1
rm class1.cpp~ class1.h~ main.cpp~ CMakeList.txt~ 


~]$ ls file_to_remove1 file_to_remove2
file_to_remove1 file_to_remove2

~]$ rm !!:1
rm file_to_remove1

~]$ rm !!:2
rm file_to_remove2

Bu gösterim numaralı bağımsız değişkeni bir komuttan alır: "$ {parametre: offset}" için belgelere buradan bakın: gnu.org/software/bash/manual/html_node/… . Daha fazla örnek için buraya bakın: howtogeek.com/howto/44997/…
Alexander Bird

1

Benzer bir ihtiyacım vardı, son komutun çıktısını bir sonrakine kullanmak istedim. Çok benzer bir | (boru). Örneğin

$ which gradle 
/usr/bin/gradle
$ ls -alrt /usr/bin/gradle

gibi bir şeye

$ which gradle |: ls -altr {}

Çözüm: Bu özel kanalı oluşturdu. Xargs kullanarak gerçekten basit -

$ alias :='xargs -I{}'

Temelde xargs için kısa bir el oluşturarak hiçbir şey, çekicilik gibi çalışır ve gerçekten kullanışlıdır. Diğer adı yalnızca .bash_profile dosyasına ekliyorum.


1

Dosya tanımlayıcıların büyüsü ve lastpipe kabuk seçeneği kullanılarak yapılabilir.

Bir komut dosyasıyla yapılması gerekir - "lastpipe" seçeneği etkileşimli modda çalışmaz.

İşte test ettiğim senaryo:

$ cat shorttest.sh 
#!/bin/bash
shopt -s lastpipe

exit_tests() {
    EXITMSG="$(cat /proc/self/fd/0)"
}

ls /bloop 2>&1 | exit_tests

echo "My output is \"$EXITMSG\""


$ bash shorttest.sh 
My output is "ls: cannot access '/bloop': No such file or directory"

Burada yaptığım şey:

  1. kabuk seçeneğini ayarlama shopt -s lastpipe. Dosya tanımlayıcısını kaybedeceğiniz için bu olmadan çalışmaz.

  2. emin benim stderr ile de yakalanan alır 2>&1

  3. çıktıyı bir işleve pipetleyerek stdin dosya tanımlayıcısına başvurulabilir.

  4. /proc/self/fd/0stdin olan dosya tanımlayıcısının içeriğini alarak değişkenin ayarlanması .

Bu yüzden bir komut dosyası hataları yakalamak için kullanıyorum, böylece bir komutla ilgili bir sorun varsa ben komut dosyası işleme durdurmak ve hemen çıkabilirsiniz.

shopt -s lastpipe

exit_tests() {
    MYSTUFF="$(cat /proc/self/fd/0)"
    BADLINE=$BASH_LINENO
}

error_msg () {
    echo -e "$0: line $BADLINE\n\t $MYSTUFF"
    exit 1
}

ls /bloop 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msg

Bu şekilde 2>&1 | exit_tests ; [[ "${PIPESTATUS[0]}" == "0" ]] || error_msgkontrol etmek istediğim her komutu arkasına ekleyebilirim .

Artık çıktılarınızın tadını çıkarabilirsiniz!


0

Bu kesinlikle bir bash çözümü değildir, ancak önceki komutların son satırını almak için sed ile borulama kullanabilirsiniz.

İlk "a" klasöründe ne var görelim

rasjani@helruo-dhcp022206::~$ find a
a
a/foo
a/bar
a/bat
a/baz
rasjani@helruo-dhcp022206::~$ 

Sonra, ls ve cd ile örnek sed & piping gibi bir şeye dönecekti:

rasjani@helruo-dhcp022206::~$ cd `find a |sed '$!d'`
rasjani@helruo-dhcp022206::~/a/baz$ pwd
/home/rasjani/a/baz
rasjani@helruo-dhcp022206::~/a/baz$

Yani, gerçek sihir sed ile gerçekleşir, sed ve sed komutlarının herhangi bir çıktısını geri keneler ile parametre olarak kullanabileceğiniz son satırı yazdırırsınız. Ya da bunu xargs ile birleştirebilirsiniz. (kabuktaki "adam xargs" senin arkadaşın)


0

Kabuk, son komutun yankı sonucunu saklayan perl benzeri özel sembollere sahip değildir.

Boru sembolünü awk ile kullanmayı öğrenin.

find . | awk '{ print "FILE:" $0 }'

Yukarıdaki örnekte şunları yapabilirsiniz:

find . -name "foo.txt" | awk '{ print "mv "$0" ~/bar/" | "sh" }'

0
find . -name foo.txt 1> tmpfile && mv `cat tmpfile` /path/to/some/dir/

kirli de olsa başka bir yol.


bulmak. -name foo.txt 1> tmpfile && mv `cat tmpfile` yolu / to / some / dir && rm tmpfile
Kevin

0

Biraz can sıkıcı olmak için komutlarımın çıkışını belirli bir dosyaya aktarmayı hatırlıyorum, benim çözümümde bir işlev .bash_profile bir dosyadaki çıktıyı yakalayan ve ihtiyacınız olduğunda sonucu döndüren .

Bunun avantajı, tüm komutu yeniden çalıştırmak zorunda kalmamanızdır (kullanırken findveya kritik olabilecek diğer uzun süreli komutları kullanırken)

Yeterince basit, bunu aşağıdakilere yapıştırın .bash_profile:

Senaryo

# catch stdin, pipe it to stdout and save to a file
catch () { cat - | tee /tmp/catch.out}
# print whatever output was saved to a file
res () { cat /tmp/catch.out }

kullanım

$ find . -name 'filename' | catch
/path/to/filename

$ res
/path/to/filename

Bu noktada, | catchtüm komutlarımın sonuna ekleme eğilimindeyim , çünkü bunu yapmanın bir maliyeti yok ve bitirmek için uzun zaman alan komutları tekrar çalıştırmamı sağlıyor.

Ayrıca, dosya çıktısını bir metin düzenleyicide açmak istiyorsanız, bunu yapabilirsiniz:

# vim or whatever your favorite text editor is
$ vim <(res)
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.