Ruby'de system () çağrılarını alma


309

Ruby'de Kernel # sistemini kullanarak bir komut çağırırsam , çıktısını nasıl alabilirim?

system("ls")


Bu çok el ipliği, teşekkürler. Komutları çalıştırma ve geri bildirim alma sınıfı, örnek kodda mükemmeldir.
ylluminate

3
Gelecekteki Google çalışanları için. Diğer sistem komut çağrıları ve aralarındaki farklar hakkında bilgi edinmek istiyorsanız, bu SO cevabına bakınız .
Özbekjon

Yanıtlar:


347

Kaosun cevabını biraz genişletmek ve açıklığa kavuşturmak istiyorum .

Komutunuzu geri tıklamayla kuşatırsanız, (açıkça) system () öğesini çağırmanıza gerek yoktur. Backticks komutu yürütür ve çıktıyı dize olarak döndürür. Daha sonra değeri şöyle bir değişkene atayabilirsiniz:

output = `ls`
p output

veya

printf output # escapes newline chars

4
komutumun bir parçası olarak bir değişken vermem gerekirse ne olur? Yani, sistem ("ls" + dosya adı) gibi bir şey backticks kullanıldığında ne anlama gelir?
Vijay Dev

47
Düzenli dizeleriyle olduğu gibi sadece ifade değerlendirme yapabilirsiniz: ls #{filename}.
Craig Walker

36
Bu yanıt tavsiye edilmez: yeni onaylanmamış kullanıcı girişi sorununu ortaya çıkarır.
Dogweather

4
@Dogweather: bu doğru olabilir, ancak diğer yöntemlerden herhangi birinden farklı mıdır?
Craig Walker

20
stderr'ı kapatmak istiyorsanız, komutunuzun sonuna 2> & 1 koyun. örneğin çıktı =command 2>&1
56'da mikrofonlu

243

Eğer kullanıcı sağlanan değerleri içeren bir dize geçmesi tüm çözümler unutmayın system, %x[]vb güvensiz! Güvenli olmayan aslında şu anlama gelir: kullanıcı, bağlamda ve programın tüm izinleriyle çalıştırmak için kodu tetikleyebilir.

Sadece söyleyebilirim systemve Open3.popen3Ruby 1.8 güvenli / kaçan bir varyant sağlamak. Ruby 1.9 IO::popenda bir dizi kabul eder.

Her seçenek ve argümanı bir dizi olarak bu çağrılardan birine geçirmeniz yeterlidir.

Yalnızca çıkış durumuna değil, muhtemelen kullanmak istediğiniz sonuca da ihtiyacınız varsa 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

Blok formunun stdin, stdout ve stderr'yi otomatik olarak kapatacağını unutmayın, aksi takdirde açıkça kapatılması gerekir .

Daha fazla bilgi burada: Ruby'de sıhhi kabuk komutları veya sistem çağrıları oluşturma


26
Soruyu gerçekten cevaplayan ve yeni soruları (onaylanmamış girdi) sunmadan sorunu çözen tek cevap budur.
Dogweather

2
Teşekkürler! Bu benim umduğum cevaptı. Bir düzeltme: getsçağrılar argümanı geçmelidir, nilaksi takdirde çıktının ilk satırını alırız. Örneğin stdout.gets(nil);
Greg Price

3
stdin, stdout ve stderr açıkça blok olmayan biçimde kapatılmalıdır .
Yarin

Ruby 2.0 veya 2.1'de bir şeyin değişip değişmediğini bilen var mı? Düzenlemeler veya yorumlar takdir edilecektir ;-)
Simon Hürlimann

1
Tartışma etrafında Open3.popen3önemli bir sorun eksik olduğunu düşünüyorum : Stdout bir boru tutabilir daha fazla veri yazan bir alt işlem varsa, alt işlem askıya alır stderr.writeve program sıkışmış stdout.gets(nil).
hagello

165

Sadece kayıt için, her ikisini de istiyorsanız (çıktı ve işlem sonucu) şunları yapabilirsiniz:

output=`ls no_existing_file` ;  result=$?.success?

4
Tam da aradığım şey buydu. Teşekkür ederim.
jdl

12
Bu sadece stdout'u yakalar ve stderr konsola gider. output=`ls no_existing_file 2>&1`; result=$?.success?
Stderr

8
Bu yanıt güvensizdir ve kullanılmamalıdır - eğer komut sabitten başka bir şeyse, ters tırnak sözdiziminin bir hataya, muhtemelen bir güvenlik açığına neden olması muhtemeldir. (Ve bu bir sabit olsa bile, muhtemelen birisinin sabit olmayan bir süre için kullanmasına ve bir hataya neden olacaktır.) Doğru bir çözüm için Simon Hürlimann'ın cevabına bakın .
Greg Price

23
Kullanıcı girişinden kaçma ihtiyacını anlamak için Greg Price'dan kudos, ancak bu cevabı yazılı olarak güvensiz olarak söylemek doğru değil. Bahsedilen Open3 yöntemi daha karmaşıktır ve daha fazla bağımlılık getirir ve birisinin "daha sonra sabit olmayan bir şekilde kullanacağı" iddiası bir piperdir. Doğru, muhtemelen bunları bir Rails uygulamasında kullanmazsınız, ancak güvenilmeyen kullanıcı girişi olasılığı olmayan basit bir sistem yardımcı programı komut dosyası için, geri tepmeler mükemmeldir ve hiç kimse bunları kullanma konusunda kötü hissetmemelidir.
sbeam

69

Basit yolu güvenli doğru bunu yapmak, yani kullanmaktır Open3.capture2(), Open3.capture2e()ya Open3.capture3().

Güvenilmeyen verilerle kullanılırsa, ruby'nin ters tırnaklarını ve %xtakma adlarını kullanmak HERHANGİ BİR DURUMDA GÜVENİLİR DEĞİLDİR . Öyle TEHLİKELİ , sade ve basit:

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

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

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

systemFonksiyon, aksine, düzgün argümanları kaçar doğru kullanıldığı takdirde :

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

Sorun, çıkış yerine çıkış kodunu döndürür ve ikincisini yakalamak kıvrımlı ve dağınıktır.

Bu konuda şimdiye kadarki en iyi cevap Open3'ten bahsediyor, ancak görev için en uygun fonksiyonlardan değil. Open3.capture2, capture2eve capture3gibi çalışır system, ancak iki veya üç bağımsız değişken döndürür:

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

Başka bir söz IO.popen(). Sözdizimi, bir diziyi girdi olarak istemesi anlamında beceriksiz olabilir, ancak aynı zamanda çalışır:

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

Kolaylık sağlamak için Open3.capture3(), örneğin bir işlevi tamamlayabilirsiniz:

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

Misal:

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

Aşağıdakileri verir:

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
Bu doğru cevap. Aynı zamanda en bilgilendiricidir. Eksik olan tek şey std * leri kapatma hakkında bir uyarıdır. Bu diğer yoruma bakın : require 'open3'; output = Open3.popen3("ls") { |stdin, stdout, stderr, wait_thr| stdout.read } Blok formunun stdin, stdout ve stderr'yi otomatik olarak kapatacağını unutmayın; aksi takdirde açıkça kapatılması gerekir .
Peter H. Boling

@ PeterH.Boling: En farkındayım, capture2, capture2eve capture3ayrıca yakın onları * otomatik s std. (En azından, hiç sorunla karşılaşmadım.)
Denis de Bernardy

Blok formunu kullanarak olmadan orada bir şeyler kapatılmalıdır bilmeniz gereken kod temeli için bir yoldur bu yüzden son derece onlar kapalı ediliyor şüpheliyim. Muhtemelen hiçbir zaman bir sorunla karşılaşmadınız, çünkü onları kapatmamak kısa ömürlü bir süreçte sorunlara neden olmaz ve uzun süren bir süreci yeterince sık başlatırsanız, std * s'yi açmazsanız otto orada görünmez. bir döngü. Linux'un vurabileceğiniz yüksek bir dosya tanıtıcı sınırı vardır, ancak vuruncaya kadar "hata" yı görmezsiniz.
Peter H. Boling

2
@ PeterH.Boling: Hayır hayır, kaynak koduna bakın. Fonksiyonlar sadece etrafında sarmalayıcıların vardır Open3#popen2, popen2eve popen3önceden tanımlanmış bir blokla: ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/...
Denis Bernardy de

1
@Dennis de Barnardy Belki de benim Ruby 2.0.0 ve farklı bir yöntemle de olsa (aynı sınıf belgelerine bağlantılı olduğunu cevapsız. Ruby-doc.org/stdlib-2.1.1/libdoc/open3/rdoc/... Örneğin itibaren : `` stdin, stdout, stderr, wait_thr = Open3.popen3 ([env,] cmd ... [, ops]) pid = wait_thr [: pid] başlatılan işlemin # pid ... stdin.close # stdin , stdout ve stderr bu formda açıkça kapatılmalıdır. stdout.close stderr.close `` Sadece belgeleri alıntı yapıyordum. "# stdin, stdout ve stderr bu formda açıkça kapatılmalıdır."
Peter H. Boling

61

Ne tür bir sonuca ihtiyacınız olduğuna bağlı olarak system () veya% x [] kullanabilirsiniz.

komut başarılı bir şekilde bulunur ve çalıştırılırsa system () işlevi true değerini, aksi halde false değerini döndürür.

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

% x [..] ise komutun sonuçlarını dize olarak kaydeder:

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

Th Jay Fields tarafından blog yazısı [..], exec ve% x ayrıntılı olarak kullanarak sistemi arasındaki farkları açıklar.


2
% X [] kullanımının ipucu için teşekkür ederiz. Sadece Mac OS X'te bir yakut komut dosyasında geri keneler kullandığım bir sorunu çözdü. Aynı komut dosyasını Cygwin'li bir Windows makinesinde çalıştırırken, geri keneler nedeniyle başarısız oldu, ancak% x [] ile çalıştı.
Henrik Warne

22

Bağımsız değişkenlerden kaçmanız gerekiyorsa, Ruby 1.9'da IO.popen ayrıca bir dizi kabul eder:

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

Önceki sürümlerde Open3.popen3'ü kullanabilirsiniz :

require "open3"

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

Stdin'i de geçmeniz gerekiyorsa, bu hem 1.9 hem de 1.8'de çalışmalıdır:

out = IO.popen("xxd -p", "r+") { |io|
    io.print "xyz"
    io.close_write
    io.read.chomp
}
p out # "78797a"

Teşekkürler! Bu harika.
Greg Price

21

Backtick kullanıyorsunuz:

`ls`

5
Ters çentikler terminalde çıkış üretmez.
Mei

3
Stderr üretmez, ancak stdout verir.
Nickolay Kondratenko

1
Stdout veya stderr'e yazmaz. Bu örneği deneyelim ruby -e '%x{ls}'- not, çıktı yok. ( %x{}
fyi

Bu harika çalıştı. Kullanmak shçıktıyı konsola (yani STDOUT) yankılayıp geri döndürür. Bu olmaz.
Joshua Pinter

19

Başka bir yol:

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

Not "boru" karakteri önce "ls" açık. Bu aynı zamanda program çıktısına veri beslemenin yanı sıra standart çıktısını okumak için de kullanılabilir.


Sadece bunu 'true' resmi dönüş değerini değil, json okumak için aws cli komutundan standart çıktı okumak için kullanılır
kraftydevil

14

Dönüş değerine ihtiyacınız varsa aşağıdakilerin yararlı olduğunu gördüm:

result = %x[ls]
puts result

Özellikle makinemdeki tüm Java işlemlerinin pidelerini listelemek istedim ve bunu kullandım:

ids = %x[ps ax | grep java | awk '{ print $1 }' | xargs]

Harika bir çözüm.
Ronan Louarn


9

Sıkıştırma veya popen kullanmak genellikle istediğiniz şey olsa da, aslında sorulan soruya cevap vermez. systemÇıktı yakalamanın geçerli nedenleri olabilir (belki otomatik test için). Küçük bir Google , başkalarının yararına burada yayınlayacağımı düşündüğüm bir cevap verdi .

Test etmek için buna ihtiyaç duyduğumdan system, örneğim , gerçek çağrı test edilen koda gömülü olduğundan standart çıktıyı yakalamak için bir blok kurulumu kullanır :

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

Bu yöntem, gerçek verileri saklamak için bir geçici dosya kullanarak verilen bloktaki herhangi bir çıktıyı yakalar. Örnek kullanım:

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

systemAramayı dahili olarak çağıran herhangi bir şeyle değiştirebilirsiniz system. İsterseniz yakalamak için benzer bir yöntem de kullanabilirsiniz stderr.


8

Çıktının bir dosyaya yeniden yönlendirilmesini istiyorsanız, Kernel#systemaşağıdaki gibi tanımlayıcıları değiştirebilirsiniz:

stdout ve stderr'i ekleme modunda bir dosyaya (/ tmp / log) yönlendirin:

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

Uzun süren bir komut için, çıktı gerçek zamanlı olarak saklanır. Ayrıca, çıktıyı bir IO.pipe kullanarak depolayabilir ve Çekirdek # sisteminden yeniden yönlendirebilirsiniz.




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.