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")


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

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


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?

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')

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 [..].


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"

Dzięki! To jest doskonałe.
Greg Price

21

Używasz backticksa:

`ls`

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.


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]

To świetne rozwiązanie.
Ronan Louarn,


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.


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 #.




Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.