วิธีเรียกคำสั่งเชลล์จาก Ruby


1077

ฉันจะเรียกคำสั่งเชลล์จากภายในโปรแกรม Ruby ได้อย่างไร ฉันจะรับเอาต์พุตจากคำสั่งเหล่านี้กลับไปเป็น Ruby ได้อย่างไร


3
ในขณะที่คำถามนี้มีประโยชน์ แต่ก็ไม่ได้ถามกัน Ruby มีหลายวิธีในการเรียก sub-shells ที่มีเอกสารดีและพบได้ง่ายโดยการอ่านเอกสารKernelและOpen3และค้นหาที่นี่ใน SO
Tin Man

1
น่าเศร้าที่หัวข้อนี้ค่อนข้างซับซ้อน Open3( docs ) เป็นตัวเลือกที่ดีที่สุดสำหรับสถานการณ์ส่วนใหญ่ IMO แต่สำหรับ Ruby รุ่นเก่าจะไม่เคารพการแก้ไขPATH( bugs.ruby-lang.org/issues/8004 ) และขึ้นอยู่กับว่าคุณผ่าน args อย่างไร (โดยเฉพาะ หากคุณใช้แฮ็พ opts ที่ไม่ใช่คำหลัก) ก็สามารถแตกได้ Open3แต่ถ้าคุณตีสถานการณ์เหล่านั้นแล้วคุณกำลังทำสิ่งที่ทันสมัยสวยและคุณสามารถคิดออกว่าจะทำอย่างไรโดยการอ่านการดำเนินงานของ
Joshua Cheek

3
ฉันประหลาดใจที่ไม่มีใครพูดถึงShellwords.escape( doc ) คุณไม่ต้องการที่จะแทรกผู้ใช้เข้าโดยตรงในคำสั่งเชลล์ - หนีมันก่อน! ดูเพิ่มเติมฉีดคำสั่ง
เคลวิน

คำตอบ:


1319

คำอธิบายนี้เป็นไปตามสคริปต์ Ruby ที่คอมเม้นต์จากเพื่อนของฉัน หากคุณต้องการปรับปรุงสคริปต์โปรดอัปเดตได้ที่ลิงก์

ครั้งแรกที่ทราบว่าเมื่อทับทิมโทรออกไปยังเปลือกก็มักจะเรียก/bin/sh, ไม่ทุบตี บางไวยากรณ์ทุบตีไม่สนับสนุน/bin/shในระบบทั้งหมด

ต่อไปนี้เป็นวิธีเรียกใช้งานเชลล์สคริปต์:

cmd = "echo 'hi'" # Sample string that can be used
  1. Kernel#` เรียกว่า backticks - `cmd`

    นี่เป็นเหมือนภาษาอื่น ๆ มากมายรวมถึง Bash, PHP และ Perl

    ส่งคืนผลลัพธ์ (เช่นเอาต์พุตมาตรฐาน) ของคำสั่งเชลล์

    เอกสาร: http://ruby-doc.org/core/Kernel.html#method-i-60

    value = `echo 'hi'`
    value = `#{cmd}`
    
  2. ไวยากรณ์ในตัว %x( cmd )

    การติดตามxตัวละครเป็นตัวคั่นซึ่งสามารถเป็นอักขระใดก็ได้ ถ้าตัวคั่นเป็นหนึ่งในตัวละคร(, [, {หรือ<, อักษรประกอบด้วยตัวอักษรขึ้นไปคั่นปิดการจับคู่การบัญชีของคู่ตัวคั่นที่ซ้อนกัน สำหรับตัวคั่นอื่นทั้งหมดตัวอักษรประกอบด้วยอักขระจนถึงอักขระตัวคั่นถัดไป #{ ... }อนุญาตให้มีการแก้ไขสตริง

    ส่งคืนผลลัพธ์ (เช่นเอาต์พุตมาตรฐาน) ของคำสั่งเชลล์เช่นเดียวกับ backticks

    เอกสาร: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-Percent+Strings

    value = %x( echo 'hi' )
    value = %x[ #{cmd} ]
    
  3. Kernel#system

    เรียกใช้งานคำสั่งที่กำหนดในเชลล์ย่อย

    ส่งคืนtrueถ้าพบคำสั่งและรันสำเร็จfalseมิฉะนั้น

    เอกสาร: http://ruby-doc.org/core/Kernel.html#method-i-system

    wasGood = system( "echo 'hi'" )
    wasGood = system( cmd )
    
  4. Kernel#exec

    แทนที่กระบวนการปัจจุบันโดยการรันคำสั่งภายนอกที่กำหนด

    ไม่ส่งคืนกระบวนการปัจจุบันถูกแทนที่และไม่ดำเนินการต่อ

    เอกสาร: http://ruby-doc.org/core/Kernel.html#method-i-exec

    exec( "echo 'hi'" )
    exec( cmd ) # Note: this will never be reached because of the line above
    

นี่คือบางส่วนคำแนะนำพิเศษ: $?ซึ่งเป็นเช่นเดียวกับ$CHILD_STATUS, เข้าถึงสถานะของระบบที่ผ่านมาคำสั่งดำเนินการถ้าคุณใช้ backticks ที่หรือsystem() %x{}จากนั้นคุณสามารถเข้าถึงexitstatusและpidคุณสมบัติ:

$?.exitstatus

สำหรับการอ่านเพิ่มเติมดู:


4
ฉันต้องการบันทึกผลลัพธ์ของการปฏิบัติการของฉันบนเซิร์ฟเวอร์การผลิต แต่ไม่พบวิธี ฉันใช้ puts #{cmd}และ logger.info ( #{cmd}) มีวิธีใดบ้างในการบันทึกผลลัพธ์ที่ได้จากการผลิต
Omer Aslam

5
และ IO # popen () และ Open3 # popen3 () mentalized.net/journal/2010/03/08/…
hughdbrown

6
เพื่อประโยชน์ของความสมบูรณ์ (อย่างที่ฉันคิดว่านี่จะเป็นคำสั่ง Ruby ด้วย): Rakeมีshซึ่งใช้ "เรียกใช้คำสั่งระบบcmdถ้าอาร์กิวเมนต์หลายตัวถูกกำหนดคำสั่งจะไม่ทำงานกับเชลล์ (ซีแมนทิสต์เหมือนกับเคอร์เนล :: exec และ Kernel :: ระบบ) "
sschuberth

40
Backticks ไม่จับ STDERR เป็นค่าเริ่มต้น ผนวก `2> & 1` เพื่อสั่งการถ้าคุณต้องการดักจับ
Andrei Botalov

14
ฉันคิดว่าคำตอบนี้จะได้รับการปรับปรุงเล็กน้อยถ้ามีการบอกว่า backticks และ% x ส่งคืน "เอาต์พุต" แทนที่จะเป็น "ผลลัพธ์" ของคำสั่งที่กำหนด หลังอาจถูกเข้าใจผิดว่าเป็นสถานะทางออก หรือนั่นเป็นเพียงฉัน
skagedal

275

24
ว้าวฮ่าฮ่า มีประโยชน์มากแม้ว่าความจริงที่ว่าสิ่งนี้จะมีอยู่นั้นเป็นเรื่องโชคร้าย
Josh Bodah

ขณะที่ทราบด้านผมพบวางไข่ () วิธีการพบในสถานที่ที่แตกต่างกัน (เช่นKernelและProcessจะเป็นมากที่สุดอเนกประสงค์มันมากขึ้นหรือน้อยลงเช่นเดียวกันกับ. PTY.spawn()แต่ทั่วไป.
Smar

160

วิธีที่ฉันชอบทำคือใช้%xตัวอักษรซึ่งทำให้ง่าย (และอ่านได้!) เพื่อใช้อัญประกาศในคำสั่งเช่น:

directorylist = %x[find . -name '*test.rb' | sort]

ซึ่งในกรณีนี้จะเติมรายการไฟล์ด้วยไฟล์ทดสอบทั้งหมดภายใต้ไดเรกทอรีปัจจุบันซึ่งคุณสามารถดำเนินการตามที่คาดไว้:

directorylist.each do |filename|
  filename.chomp!
  # work with file
end

4
ไม่%x[ cmd ]ส่งกลับอาร์เรย์ให้คุณ?
x-yuri

2
ข้างต้นใช้งานไม่ได้สำหรับฉัน `` <main> ': วิธีที่ไม่ได้กำหนดeach' for :String (NoMethodError) มันใช้ได้ผลกับคุณอย่างไร ฉันกำลังใช้ruby -v ruby 1.9.3p484 (2013-11-22 revision 43786) [i686-linux]คุณแน่ใจหรือไม่ว่าอาร์เรย์ถูกส่งคืนจากคำสั่งเพื่อให้ลูปใช้งานได้จริง?
Nasser

% x [cmd] .split ("\ n") จะแสดงรายการกลับ :)
Ian Ellis

65

นี่คือบทความที่ดีที่สุดในความคิดของฉันเกี่ยวกับการเรียกใช้สคริปต์เชลล์ใน Ruby: " 6 วิธีในการเรียกใช้คำสั่งของเชลล์ใน Ruby "

หากคุณต้องการเพียงรับเอาท์พุทให้ใช้ backticks

ฉันต้องการสิ่งขั้นสูงเพิ่มเติมเช่น STDOUT และ STDERR ดังนั้นฉันจึงใช้อัญมณี Open4 คุณมีวิธีการอธิบายทั้งหมดที่นั่น


2
โพสต์ที่อธิบายไว้ที่นี่ไม่ได้กล่าวถึง%xตัวเลือกไวยากรณ์
เหม่ย

+1 สำหรับ Open4 ฉันเริ่มพยายามใช้spawnวิธีการในเวอร์ชันของฉันเองเมื่อฉันพบสิ่งนี้
Brandan

40

สิ่งที่ฉันชอบคือOpen3

  require "open3"

  Open3.popen3('nroff -man') { |stdin, stdout, stderr| ... }

2
ฉันยังชอบ Open3 โดยเฉพาะอย่างยิ่ง Open3.capture3: ruby-doc.org/stdlib-1.9.3/libdoc/open3/rdoc/... -> stdout, stderr, status = Open3.capture3('nroff -man', :stdin_data => stdin)
SEVERIN

มีเอกสารเกี่ยวกับวิธีการทดสอบ Spec และ Unit ด้วย Open3 หรือ Open อื่น ๆ ใน Ruby std-lib หรือไม่? มันยากที่จะทดสอบ shell ที่ระดับความเข้าใจปัจจุบันของฉัน
FilBot3

29

บางสิ่งที่ควรพิจารณาเมื่อเลือกระหว่างกลไกเหล่านี้คือ:

  1. คุณเพียงแค่ต้องการ stdout หรือคุณต้องการ stderr เช่นกัน? หรือแยกออกจากกัน?
  2. ผลงานของคุณใหญ่แค่ไหน? คุณต้องการเก็บผลลัพธ์ทั้งหมดไว้ในหน่วยความจำหรือไม่?
  3. คุณต้องการอ่านเอาต์พุตของคุณบางส่วนขณะที่ subprocess ยังทำงานอยู่หรือไม่?
  4. คุณต้องการรหัสผลลัพธ์หรือไม่?
  5. คุณต้องการอ็อบเจกต์ Ruby ที่แทนกระบวนการและให้คุณฆ่ามันตามต้องการหรือไม่?

คุณอาจจะต้องการอะไรจาก backticks ง่าย ( ``) system()และIO.popenจะเต็มเป่าKernel.fork/ Kernel.execด้วยและIO.pipeIO.select

คุณอาจต้องการทิ้งการหมดเวลาลงในการผสมหากกระบวนการย่อยใช้เวลาดำเนินการนานเกินไป

แต่น่าเสียดายที่มันมากขึ้น


25

อีกหนึ่งตัวเลือก:

เมื่อคุณ:

  • ต้องการ stderr เช่นเดียวกับ stdout
  • ไม่สามารถ / ไม่ใช้ Open3 / Open4 (พวกเขาโยนข้อยกเว้นใน NetBeans บน Mac ของฉันไม่รู้ว่าทำไม)

คุณสามารถใช้การเปลี่ยนเส้นทางเชลล์:

puts %x[cat bogus.txt].inspect
  => ""

puts %x[cat bogus.txt 2>&1].inspect
  => "cat: bogus.txt: No such file or directory\n"

2>&1ไวยากรณ์งานทั่วLinux , Mac และWindows ที่ตั้งแต่วันแรกของ MS-DOS


25

ฉันไม่ใช่ผู้เชี่ยวชาญทับทิมแน่นอน แต่ฉันจะให้มัน:

$ irb 
system "echo Hi"
Hi
=> true

คุณควรทำสิ่งต่าง ๆ เช่น:

cmd = 'ls'
system(cmd)

21

คำตอบข้างต้นค่อนข้างดีอยู่แล้ว แต่ฉันต้องการแบ่งปันบทความสรุปต่อไปนี้: " 6 วิธีในการเรียกใช้คำสั่งของเชลล์ในรูบี "

โดยพื้นฐานแล้วจะบอกเรา:

Kernel#exec:

exec 'echo "hello $HOSTNAME"'

systemและ$?:

system 'false' 
puts $?

Backticks (`):

today = `date`

IO#popen:

IO.popen("date") { |f| puts f.gets }

Open3#popen3 - stdlib:

require "open3"
stdin, stdout, stderr = Open3.popen3('dc') 

Open4#popen4 - อัญมณี:

require "open4" 
pid, stdin, stdout, stderr = Open4::popen4 "false" # => [26327, #<IO:0x6dff24>, #<IO:0x6dfee8>, #<IO:0x6dfe84>]

15

หากคุณต้องการ Bash จริงๆโปรดทราบในคำตอบ "ดีที่สุด"

ครั้งแรกที่ทราบว่าเมื่อทับทิมโทรออกไปยังเปลือกก็มักจะเรียก/bin/sh, ไม่ทุบตี บางไวยากรณ์ทุบตีไม่สนับสนุน/bin/shในระบบทั้งหมด

หากคุณต้องการใช้ Bash ให้ใส่bash -c "your Bash-only command"วิธีการโทรที่คุณต้องการ:

quick_output = system("ls -la")
quick_bash = system("bash -c 'ls -la'")

ทดสอบ:

system("echo $SHELL")
system('bash -c "echo $SHELL"')

หรือถ้าคุณกำลังเรียกใช้ไฟล์สคริปต์ที่มีอยู่เช่น

script_output = system("./my_script.sh")

ทับทิมควรให้เกียรติ shebang แต่คุณสามารถใช้ได้เสมอ

system("bash ./my_script.sh")

เพื่อให้แน่ใจว่าแม้ว่าอาจมีค่าใช้จ่ายเล็กน้อยจากการ/bin/shทำงาน/bin/bashแต่คุณอาจไม่สังเกตเห็น


11

คุณยังสามารถใช้ตัวดำเนินการ backtick (`) คล้ายกับ Perl:

directoryListing = `ls /`
puts directoryListing # prints the contents of the root directory

มีประโยชน์ถ้าคุณต้องการอะไรที่เรียบง่าย

วิธีการที่คุณต้องการใช้นั้นขึ้นอยู่กับว่าคุณพยายามทำอะไรให้สำเร็จ ตรวจสอบเอกสารสำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการต่าง ๆ


10

เราสามารถทำได้หลายวิธี

ใช้Kernel#execไม่มีอะไรหลังจากคำสั่งนี้จะถูกดำเนินการ:

exec('ls ~')

การใช้ backticks or %x

`ls ~`
=> "Applications\nDesktop\nDocuments"
%x(ls ~)
=> "Applications\nDesktop\nDocuments"

การใช้Kernel#systemคำสั่งส่งคืนtrueถ้าสำเร็จfalseถ้าไม่สำเร็จและส่งคืนnilหากการดำเนินการคำสั่งล้มเหลว:

system('ls ~')
=> true


9

ใช้คำตอบที่นี่และเชื่อมโยงกับคำตอบของ Mihai ฉันรวบรวมฟังก์ชั่นที่ตรงตามข้อกำหนดเหล่านี้:

  1. รวบรวม STDOUT และ STDERR อย่างเรียบร้อยเพื่อไม่ให้ "รั่วไหล" เมื่อสคริปต์ของฉันถูกเรียกใช้จากคอนโซล
  2. อนุญาตให้ส่งผ่านอาร์กิวเมนต์ไปยังเชลล์เป็นอาร์เรย์ดังนั้นไม่จำเป็นต้องกังวลเกี่ยวกับการหลบหนี
  3. จับสถานะการออกของคำสั่งเพื่อให้ชัดเจนเมื่อเกิดข้อผิดพลาด

เป็นโบนัสอันนี้จะส่งคืน STDOUT ในกรณีที่คำสั่ง shell ออกจากสำเร็จ (0) และวางอะไรบน STDOUT ในลักษณะนี้มันแตกต่างจากsystemเพียงแค่ส่งกลับtrueในกรณีดังกล่าว

รหัสดังนี้ ฟังก์ชั่นเฉพาะคือsystem_quietly:

require 'open3'

class ShellError < StandardError; end

#actual function:
def system_quietly(*cmd)
  exit_status=nil
  err=nil
  out=nil
  Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thread|
    err = stderr.gets(nil)
    out = stdout.gets(nil)
    [stdin, stdout, stderr].each{|stream| stream.send('close')}
    exit_status = wait_thread.value
  end
  if exit_status.to_i > 0
    err = err.chomp if err
    raise ShellError, err
  elsif out
    return out.chomp
  else
    return true
  end
end

#calling it:
begin
  puts system_quietly('which', 'ruby')
rescue ShellError
  abort "Looks like you don't have the `ruby` command. Odd."
end

#output: => "/Users/me/.rvm/rubies/ruby-1.9.2-p136/bin/ruby"

9

อย่าลืมspawnคำสั่งเพื่อสร้างกระบวนการพื้นหลังเพื่อรันคำสั่งที่ระบุ คุณสามารถรอให้การเรียนจบโดยใช้Processคลาสและการคืนค่าpid:

pid = spawn("tar xf ruby-2.0.0-p195.tar.bz2")
Process.wait pid

pid = spawn(RbConfig.ruby, "-eputs'Hello, world!'")
Process.wait pid

หมอบอกว่า: วิธีนี้คล้ายกับ#systemแต่ไม่ต้องรอให้คำสั่งเสร็จสิ้น


2
Kernel.spawn()ดูเหมือนจะมีความหลากหลายมากกว่าตัวเลือกอื่น ๆ ทั้งหมด
Kashyap

6

หากคุณมีกรณีที่มีความซับซ้อนกว่ากรณีทั่วไปที่ไม่สามารถจัดการกับการแล้วตรวจสอบ`` Kernel.spawn()นี่น่าจะเป็นคุณสมบัติทั่วไป / เต็มรูปแบบที่จัดทำโดยหุ้นของ Ruby เพื่อดำเนินการคำสั่งภายนอก

คุณสามารถใช้มันเพื่อ:

  • สร้างกลุ่มกระบวนการ (Windows)
  • เปลี่ยนเส้นทางใน, ออก, ข้อผิดพลาดไปยังไฟล์ / กันและกัน
  • ตั้งค่า env vars, umask
  • เปลี่ยนไดเรกทอรีก่อนดำเนินการคำสั่ง
  • กำหนดขีด จำกัด ทรัพยากรสำหรับ CPU / data / etc
  • ทำทุกอย่างที่สามารถทำได้ด้วยตัวเลือกอื่นในคำตอบอื่น ๆ แต่ด้วยรหัสเพิ่มเติม

เอกสารทับทิมมีตัวอย่างที่ดีพอ:

env: hash
  name => val : set the environment variable
  name => nil : unset the environment variable
command...:
  commandline                 : command line string which is passed to the standard shell
  cmdname, arg1, ...          : command name and one or more arguments (no shell)
  [cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell)
options: hash
  clearing environment variables:
    :unsetenv_others => true   : clear environment variables except specified by env
    :unsetenv_others => false  : dont clear (default)
  process group:
    :pgroup => true or 0 : make a new process group
    :pgroup => pgid      : join to specified process group
    :pgroup => nil       : dont change the process group (default)
  create new process group: Windows only
    :new_pgroup => true  : the new process is the root process of a new process group
    :new_pgroup => false : dont create a new process group (default)
  resource limit: resourcename is core, cpu, data, etc.  See Process.setrlimit.
    :rlimit_resourcename => limit
    :rlimit_resourcename => [cur_limit, max_limit]
  current directory:
    :chdir => str
  umask:
    :umask => int
  redirection:
    key:
      FD              : single file descriptor in child process
      [FD, FD, ...]   : multiple file descriptor in child process
    value:
      FD                        : redirect to the file descriptor in parent process
      string                    : redirect to file with open(string, "r" or "w")
      [string]                  : redirect to file with open(string, File::RDONLY)
      [string, open_mode]       : redirect to file with open(string, open_mode, 0644)
      [string, open_mode, perm] : redirect to file with open(string, open_mode, perm)
      [:child, FD]              : redirect to the redirected file descriptor
      :close                    : close the file descriptor in child process
    FD is one of follows
      :in     : the file descriptor 0 which is the standard input
      :out    : the file descriptor 1 which is the standard output
      :err    : the file descriptor 2 which is the standard error
      integer : the file descriptor of specified the integer
      io      : the file descriptor specified as io.fileno
  file descriptor inheritance: close non-redirected non-standard fds (3, 4, 5, ...) or not
    :close_others => false : inherit fds (default for system and exec)
    :close_others => true  : dont inherit (default for spawn and IO.popen)

6

วิธี backticks (`) เป็นวิธีที่ง่ายที่สุดในการเรียกคำสั่งเชลล์จาก Ruby ส่งคืนผลลัพธ์ของคำสั่งเชลล์:

     url_request = 'http://google.com'
     result_of_shell_command = `curl #{url_request}`

5

ได้รับคำสั่งเช่นattrib:

require 'open3'

a="attrib"
Open3.popen3(a) do |stdin, stdout, stderr|
  puts stdout.read
end

ฉันพบว่าในขณะที่วิธีนี้ไม่น่าจดจำเท่า

system("thecommand")

หรือ

`thecommand`

ใน backticks สิ่งที่ดีเกี่ยวกับวิธีนี้เมื่อเทียบกับวิธีอื่นคือ backticks ดูเหมือนจะไม่ให้putsคำสั่งที่ฉันเรียกใช้ / เก็บคำสั่งที่ฉันต้องการเรียกใช้ในตัวแปรและsystem("thecommand")ดูเหมือนจะไม่ให้ฉันได้รับผลลัพธ์ในขณะที่ วิธีนี้ให้ฉันทำทั้งสองอย่างและให้ฉันเข้าถึง stdin, stdout และ stderr อย่างอิสระ

โปรดดูที่ " การดำเนินการคำสั่งในทับทิม " และเอกสาร Open3 ทับทิม


3

นี่ไม่ใช่คำตอบที่แท้จริง แต่อาจมีบางคนพบว่ามีประโยชน์:

เมื่อใช้ TK GUI บน Windows และคุณจำเป็นต้องเรียกคำสั่งเชลล์จาก rubyw คุณจะมีหน้าต่าง CMD ที่น่ารำคาญปรากฏขึ้นมาเป็นเวลาไม่ถึงวินาที

เพื่อหลีกเลี่ยงปัญหานี้คุณสามารถใช้:

WIN32OLE.new('Shell.Application').ShellExecute('ipconfig > log.txt','','','open',0)

หรือ

WIN32OLE.new('WScript.Shell').Run('ipconfig > log.txt',0,0)

ทั้งสองจะเก็บipconfigเอาท์พุทภายในlog.txtแต่จะไม่มีหน้าต่างขึ้นมา

คุณจะต้องเข้าไปrequire 'win32ole'ข้างในสคริปต์ของคุณ

system(), exec()และspawn()ทั้งหมดจะปรากฏขึ้นที่หน้าต่างที่น่ารำคาญเมื่อใช้ TK และ rubyw


-2

ต่อไปนี้เป็นสิ่งที่ยอดเยี่ยมที่ฉันใช้ใน ruby ​​script ใน OS X (เพื่อให้ฉันสามารถเริ่มต้นสคริปต์และรับการอัปเดตแม้หลังจากสลับจากหน้าต่าง):

cmd = %Q|osascript -e 'display notification "Server was reset" with title "Posted Update"'|
system ( cmd )
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.