Jeśli wywołam polecenie za pomocą systemu jądra # w Ruby, jak mogę uzyskać jego wynik?
system("ls")
Jeśli wywołam polecenie za pomocą systemu jądra # w Ruby, jak mogę uzyskać jego wynik?
system("ls")
Odpowiedzi:
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
ls #{filename}
.
command 2>&1
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ć system
i Open3.popen3
zapewniam bezpieczny / uciekający wariant w Ruby 1.8. W Ruby 1.9 IO::popen
akceptuje 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
gets
wywołania powinny przekazać argument nil
, ponieważ w przeciwnym razie otrzymamy pierwszy wiersz wyniku. Więc np stdout.gets(nil)
.
Open3.popen3
brakuje 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)
.
Dla przypomnienia, jeśli chcesz zarówno (wynik i wynik operacji) możesz:
output=`ls no_existing_file` ; result=$?.success?
output=`ls no_existing_file 2>&1`; result=$?.success?
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 %x
aliasu 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 system
funkcja 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
, capture2e
A capture3
praca 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')
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 .
capture2
, capture2e
icapture3
także automatycznie zamknij je std * s. (Przynajmniej nigdy nie spotkałem się z tym problemem).
Open3#popen2
, popen2e
i popen3
z góry określonego bloku: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
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 [..].
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"
Używasz backticksa:
`ls`
ruby -e '%x{ls}'
- uwaga, brak danych wyjściowych. (fyi %x{}
jest równoważne z
sh
spowoduje echo wyjścia do konsoli (tj. STDOUT), a także zwróci go. To nie
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.
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]
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 .
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 system
danych 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 system
wywoł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ć system
rozmowę dowolnym połączeniem wewnętrznym system
. Możesz także użyć podobnej metody przechwytywania, stderr
jeśli chcesz.
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 #.
Jako bezpośrednią wymianę systemu (...) możesz użyć Open3.popen3 (...)
Dalsza dyskusja: http://tech.natemurray.com/2007/03/ruby-shell-commands.html
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
puts `date`
puts $?
Mon Mar 7 19:01:15 PST 2016
pid 13093 exit 0