Pobieranie danych wyjściowych wywołań system () w Ruby

309

Jeśli wywołam polecenie za pomocą systemu jądra # w Ruby, jak mogę uzyskać jego wynik?

system("ls")
Macha
źródło
1
Możesz rzucić
Manrico Corazzi
To bardzo ręczny wątek, dzięki. Klasa do uruchamiania poleceń i uzyskiwania informacji zwrotnych jest świetna w przykładowym kodzie.
pokaż
3
Dla przyszłych pracowników Google. Jeśli chcesz dowiedzieć się o innych wywołaniach poleceń systemowych i ich różnicach, zapoznaj się z odpowiedzią SO .
Uzbekjon

Odpowiedzi:

347

Chciałbym trochę rozszerzyć i wyjaśnić odpowiedź na chaos .

Jeśli otoczysz swoje polecenie backtickami, to wcale nie musisz (jawnie) wywoływać system (). Backticks wykonują polecenie i zwracają dane wyjściowe jako ciąg. Następnie możesz przypisać wartość do zmiennej takiej jak:

output = `ls`
p output

lub

printf output # escapes newline chars
Craig Walker
źródło
4
co jeśli muszę podać zmienną jako część mojego polecenia? Oznacza to, co przełożyłoby się na coś takiego jak system („ls” + nazwa pliku), kiedy mają być używane backtyki?
Vijay Dev
47
Można to zrobić ocenę ekspresji tak samo jak regularne ciągi: ls #{filename}.
Craig Walker,
36
Ta odpowiedź nie jest wskazana: wprowadza nowy problem niezamierzonego wprowadzania danych przez użytkownika.
Dogweather
4
@Dogweather: to może być prawda, ale czy różni się ona od innych metod?
Craig Walker
20
jeśli chcesz przechwycić stderr, po prostu umieść 2> i 1 na końcu swojej komendy. np. wyjście =command 2>&1
micred
243

Należy pamiętać, że wszystkie rozwiązania, gdzie przechodzą ciąg zawierający warunkiem użytkownika wartości system, %x[]itp są niebezpieczne! Niebezpieczne oznacza w rzeczywistości: użytkownik może uruchomić kod w celu uruchomienia w kontekście i przy wszystkich uprawnieniach programu.

O ile mogę tylko powiedzieć systemi Open3.popen3zapewniam bezpieczny / uciekający wariant w Ruby 1.8. W Ruby 1.9 IO::popenakceptuje również tablicę.

Po prostu przekaż każdą opcję i argument jako tablicę do jednego z tych wywołań.

Jeśli potrzebujesz nie tylko statusu wyjścia, ale także wyniku, którego prawdopodobnie chcesz użyć Open3.popen3:

require 'open3'
stdin, stdout, stderr, wait_thr = Open3.popen3('usermod', '-p', @options['shadow'], @options['username'])
stdout.gets(nil)
stdout.close
stderr.gets(nil)
stderr.close
exit_code = wait_thr.value

Zauważ, że forma bloku automatycznie zamknie stdin, stdout i stderr - w przeciwnym razie będą musiały zostać jawnie zamknięte .

Więcej informacji tutaj: Tworzenie poleceń powłoki sanitarnej lub wywołań systemowych w Ruby

Simon Hürlimann
źródło
26
Jest to jedyna odpowiedź, która faktycznie odpowiada na pytanie i rozwiązuje problem bez wprowadzania nowych (niezamówione dane wejściowe).
Dogweather
2
Dzięki! Jest to odpowiedź, na którą liczyłem. Jedna korekta: getswywołania powinny przekazać argument nil, ponieważ w przeciwnym razie otrzymamy pierwszy wiersz wyniku. Więc np stdout.gets(nil).
Greg Price
3
stdin, stdout i stderr powinny być jawnie zamknięte w formie nieblokowanej .
Yarin
Czy ktoś wie, czy coś się zmieniło w Ruby 2.0 lub 2.1? Będziemy wdzięczni za zmiany lub komentarze ;-)
Simon Hürlimann
1
Myślę, że w dyskusji Open3.popen3brakuje poważnego problemu: jeśli masz podproces, który zapisuje na standardowe wyjście więcej danych niż jest w stanie pomieścić potok, podproces zostaje zawieszony stderr.write, a twój program utknie stdout.gets(nil).
hagello
165

Dla przypomnienia, jeśli chcesz zarówno (wynik i wynik operacji) możesz:

output=`ls no_existing_file` ;  result=$?.success?
FernandoFabreti
źródło
4
Właśnie tego szukałem. Dziękuję Ci.
jdl
12
To tylko przechwytuje stdout, a stderr idzie do konsoli. Aby uzyskać stderr, użyj: output=`ls no_existing_file 2>&1`; result=$?.success?
peterept
8
Ta odpowiedź jest niebezpieczna i nie należy jej używać - jeśli polecenie nie jest stałe, wówczas składnia cofnięcia może spowodować błąd, prawdopodobnie lukę w zabezpieczeniach. (I nawet jeśli jest to stała, prawdopodobnie spowoduje to, że ktoś użyje jej do niestałości później i spowoduje błąd). Zobacz poprawne rozwiązanie Simona Hürlimanna .
Greg Price
23
Wyrazy uznania dla Grega Price za zrozumienie potrzeby ucieczki od danych wejściowych od użytkownika, ale twierdzenie, że ta odpowiedź w formie pisemnej jest niebezpieczne, jest niewłaściwe. Wspomniana metoda Open3 jest bardziej skomplikowana i wprowadza więcej zależności, a argumentem, że ktoś „użyje jej do niestałości później” jest słaby. To prawda, że ​​prawdopodobnie nie użyłbyś ich w aplikacji Railsowej, ale w przypadku prostego skryptu narzędzia systemowego bez możliwości niezaufanego wprowadzania danych przez użytkownika backticks są w porządku i nikt nie powinien czuć się źle z ich użyciem.
sbeam
69

Prosta droga to zrobić prawidłowo i bezpiecznie jest w użyciu Open3.capture2(), Open3.capture2e()lub Open3.capture3().

Używanie odwrotności ruby ​​i jej %xaliasu NIE JEST BEZPIECZNE W ŻADNYM OKOLICY, jeśli zostanie użyte z niezaufanymi danymi. Jest NIEBEZPIECZNY , prosty i prosty:

untrusted = "; date; echo"
out = `echo #{untrusted}`                              # BAD

untrusted = '"; date; echo"'
out = `echo "#{untrusted}"`                            # BAD

untrusted = "'; date; echo'"
out = `echo '#{untrusted}'`                            # BAD

Z kolei systemfunkcja poprawnie unika argumentów, jeśli jest używana poprawnie :

ret = system "echo #{untrusted}"                       # BAD
ret = system 'echo', untrusted                         # good

Problem w tym, że zwraca kod wyjścia zamiast wyniku, a przechwycenie tego ostatniego jest skomplikowane i nieuporządkowane.

Najlepsza odpowiedź w tym wątku jak dotąd wspomina o Open3, ale nie o funkcjach, które najlepiej pasują do tego zadania. Open3.capture2, capture2eA capture3praca jak system, ale powróci dwa lub trzy argumenty:

out, err, st = Open3.capture3("echo #{untrusted}")     # BAD
out, err, st = Open3.capture3('echo', untrusted)       # good
out_err, st  = Open3.capture2e('echo', untrusted)      # good
out, st      = Open3.capture2('echo', untrusted)       # good
p st.exitstatus

Kolejne wspomnienia IO.popen(). Składnia może być niezdarna w tym sensie, że chce tablicy jako danych wejściowych, ale działa również:

out = IO.popen(['echo', untrusted]).read               # good

Dla wygody możesz zawinąć Open3.capture3()funkcję, np .:

#
# Returns stdout on success, false on failure, nil on error
#
def syscall(*cmd)
  begin
    stdout, stderr, status = Open3.capture3(*cmd)
    status.success? && stdout.slice!(0..-(1 + $/.size)) # strip trailing eol
  rescue
  end
end

Przykład:

p system('foo')
p syscall('foo')
p system('which', 'foo')
p syscall('which', 'foo')
p system('which', 'which')
p syscall('which', 'which')

Daje następujące:

nil
nil
false
false
/usr/bin/which         <— stdout from system('which', 'which')
true                   <- p system('which', 'which')
"/usr/bin/which"       <- p syscall('which', 'which')
Denis de Bernardy
źródło
2
To jest poprawna odpowiedź. Jest również najbardziej pouczający. Brakuje tylko ostrzeżenia o zamknięciu standardowych plików. Zobacz ten inny komentarz : require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } Zauważ, że forma bloku automatycznie zamyka stdin, stdout i stderr - w przeciwnym razie musiałyby zostać jawnie zamknięte .
Peter H. Boling
@ PeterH. Boling: Najlepsze, co wiem capture2, capture2eicapture3 także automatycznie zamknij je std * s. (Przynajmniej nigdy nie spotkałem się z tym problemem).
Denis de Bernardy
bez użycia formularza blokowego nie ma sposobu, aby baza kodów wiedziała, kiedy coś powinno zostać zamknięte, więc bardzo wątpię, aby zostały zamknięte. Prawdopodobnie nigdy nie natknąłeś się na problem, ponieważ nie zamknięcie go nie spowoduje problemów w krótkotrwałym procesie, a jeśli wystarczająco często restartujesz długotrwały proces, otto też się tam nie pojawi, chyba że otwierasz std * s w pętla. Linux ma wysoki limit deskryptorów plików, do którego można trafić, ale dopóki go nie osiągniesz, nie zobaczysz „błędu”.
Peter H. Boling
2
@ PeterH.Boling: Nie, nie, zobacz kod źródłowy. Funkcje są tylko owijarki wokół Open3#popen2, popen2ei popen3z góry określonego bloku: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
Denis de Bernardy
1
@Dennis de Barnardy Być może nie zauważyłeś, że podłączyłem tę samą dokumentację klasową (chociaż dla Ruby 2.0.0 i inną metodę. Ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/… Z przykładu : `` `stdin, stdout, stderr, wait_thr = Open3.popen3 ([env,] cmd ... [, opts]) pid = wait_thr [: pid] # pid rozpoczętego procesu ... stdin.close # stdin , stdout i stderr powinny być jawnie zamknięte w tej formie. stdout.close stderr.close `` Właśnie cytowałem dokumentację. "# stdin, stdout i stderr powinny być jawnie zamknięte w tej formie."
Peter H. Boling
61

Możesz użyć system () lub% x [] w zależności od tego, jaki wynik potrzebujesz.

system () zwraca true, jeśli polecenie zostało znalezione i uruchomione pomyślnie, w przeciwnym razie false.

>> s = system 'uptime'
10:56  up 3 days, 23:10, 2 users, load averages: 0.17 0.17 0.14
=> true
>> s.class
=> TrueClass
>> $?.class
=> Process::Status

Z drugiej strony% x [..] zapisuje wyniki polecenia jako ciąg znaków:

>> result = %x[uptime]
=> "13:16  up 4 days,  1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> p result 
"13:16  up 4 days,  1:30, 2 users, load averages: 0.39 0.29 0.23\n"
>> result.class
=> String

W blogu Jaya Fieldsa szczegółowo wyjaśniono różnice między używaniem systemu, exec i% x [..].

Martin Gross
źródło
2
Dziękujemy za wskazówkę dotyczącą używania% x []. Właśnie rozwiązałem problem, w którym użyłem tylnych znaczników w skrypcie ruby ​​w Mac OS X. Podczas uruchamiania tego samego skryptu na komputerze z systemem Windows z Cygwin, nie powiodło się z powodu tylnych znaczników, ale działało z% x [].
Henrik Warne,
22

Jeśli chcesz uniknąć argumentów, w Ruby 1.9 IO.popen akceptuje również tablicę:

p IO.popen(["echo", "it's escaped"]).read

We wcześniejszych wersjach można używać Open3.popen3 :

require "open3"

Open3.popen3("echo", "it's escaped") { |i, o| p o.read }

Jeśli musisz również przekazać standardowe wejście, powinno to działać zarówno w wersji 1.9, jak i 1.8:

out = IO.popen("xxd -p", "r+") { |io|
    io.print "xyz"
    io.close_write
    io.read.chomp
}
p out # "78797a"
Lri
źródło
Dzięki! To jest doskonałe.
Greg Price
21

Używasz backticksa:

`ls`
chaos
źródło
5
Wsteczne nie wytwarzają danych wyjściowych na terminalu.
Mei
3
Nie wytwarza stderr, ale daje stdout.
Nickolay Kondratenko
1
Nie pisze do stdout ani stderr. Spróbujmy tego przykładu ruby -e '%x{ls}'- uwaga, brak danych wyjściowych. (fyi %x{}jest równoważne z
atakami wstecznymi
To działało świetnie. Użycie shspowoduje echo wyjścia do konsoli (tj. STDOUT), a także zwróci go. To nie
Joshua Pinter
19

Innym sposobem jest:

f = open("|ls")
foo = f.read()

Zauważ, że to znak „potoku” przed „ls” w otwartym. Można to również wykorzystać do wprowadzenia danych do standardowego wejścia programów, jak również do odczytu standardowego wyjścia.

dwc
źródło
Po prostu użyłem tego do odczytu standardowego wyjścia z komendy aws cli w celu odczytania pliku json, a nie oficjalnej wartości zwracanej „true”
kraftydevil
14

Stwierdziłem, że poniższe informacje są przydatne, jeśli potrzebujesz wartości zwracanej:

result = %x[ls]
puts result

W szczególności chciałem wyświetlić listę wszystkich procesów Java na moim komputerze i użyłem tego:

ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]
Geoff van der Meer
źródło
To świetne rozwiązanie.
Ronan Louarn,
13

Jak już wyjaśnił Simon Hürlimann , Open3 jest bezpieczniejszy niż backticks itp.

require 'open3'
output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read }

Zauważ, że forma bloku automatycznie zamknie stdin, stdout i stderr - w przeciwnym razie będą musiały zostać jawnie zamknięte .

Yarin
źródło
9

Podczas gdy używanie backticks lub popen jest często tym, czego naprawdę chcesz, tak naprawdę nie odpowiada na zadane pytanie. Mogą istnieć ważne powody przechwytywania systemdanych wyjściowych (być może w przypadku testów automatycznych). Trochę Googlinga znalazło odpowiedź, o której pomyślałem, że opublikuję tutaj z korzyścią dla innych.

Ponieważ potrzebowałem tego do testowania, mój przykład wykorzystuje konfigurację bloku do przechwycenia standardowego wyjścia, ponieważ rzeczywiste systemwywołanie jest zakopane w testowanym kodzie:

require 'tempfile'

def capture_stdout
  stdout = $stdout.dup
  Tempfile.open 'stdout-redirect' do |temp|
    $stdout.reopen temp.path, 'w+'
    yield if block_given?
    $stdout.reopen stdout
    temp.read
  end
end

Ta metoda przechwytuje dane wyjściowe w danym bloku przy użyciu pliku tymczasowego do przechowywania rzeczywistych danych. Przykładowe użycie:

captured_content = capture_stdout do
  system 'echo foo'
end
puts captured_content

Możesz zastąpić systemrozmowę dowolnym połączeniem wewnętrznym system. Możesz także użyć podobnej metody przechwytywania, stderrjeśli chcesz.

Eric Anderson
źródło
8

Jeśli chcesz przekierować dane wyjściowe do pliku Kernel#system, możesz zmodyfikować deskryptory w następujący sposób:

przekieruj stdout i stderr do pliku (/ tmp / log) w trybie dołączania:

system('ls -al', :out => ['/tmp/log', 'a'], :err => ['/tmp/log', 'a'])

W przypadku długo działającego polecenia spowoduje to zapisanie danych wyjściowych w czasie rzeczywistym. Możesz również przechowywać dane wyjściowe za pomocą IO.pipe i przekierować je z systemu jądra #.

Ashrith
źródło
0

Nie znalazłem tego tutaj, więc dodając go, miałem problemy z uzyskaniem pełnej wydajności.

Możesz przekierować STDERR do STDOUT, jeśli chcesz przechwycić STDERR za pomocą backsticka.

output = `grep hosts / private / etc / * 2> & 1`

źródło: http://blog.bigbinary.com/2012/10/18/backtick-system-exec-in-ruby.html

dac2009
źródło
-1
puts `date`
puts $?


Mon Mar  7 19:01:15 PST 2016
pid 13093 exit 0
Ron Hinchley
źródło