Thực thi các lệnh bash từ Rakefile


76

Tôi muốn thực hiện một số bashlệnh từ a Rakefile.

Tôi đã thử những điều sau đây trong Rakefile

task :hello do
  %{echo "World!"}
end

nhưng khi thực hiện rake hellokhông có đầu ra? Làm cách nào để thực hiện các lệnh bash từ Rakefile?

LƯU Ý : Đây không phải là một bản sao vì nó hỏi cụ thể cách thực hiện các lệnh bash từ Rakefile .


Nó không phải %{, nó %x(và nó trả về stdout dưới dạng một chuỗi thay vì in nó.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

8
Nó không phải là bản sao như đã nêu ở trên, vì nó đặc biệt yêu cầu thực thi trong tệp rake chứ không phải trong chương trình ruby ​​thông thường.
Gizmomogwai

Tại sao nó được đánh dấu là bản sao khi nó hỏi cụ thể về Rake hơn là Ruby nói chung ?!
silverdr

Câu trả lời:


131

Tôi nghĩ cách mà rake muốn điều này xảy ra là với: http://rubydoc.info/gems/rake/FileUtils#sh-instance_method Ví dụ:

task :test do
  sh "ls"
end

Hàm rake tích hợp sh sẽ đảm nhận giá trị trả về của lệnh (tác vụ không thành công nếu lệnh có giá trị trả về khác 0) và ngoài ra nó cũng xuất kết quả đầu ra của các lệnh.


7
Các giải pháp khác như thực thi với backticks, v.v. không phải là phương pháp cào mà là phương pháp ruby ​​(xem zhangxh.net/programming/ruby/… để biết 6 cách khác nhau). Những gì rake làm trong hàm "sh" là tự động quan tâm đến giá trị trả về của lệnh, do đó tác vụ rake không thành công, nếu một lệnh trong sh không thành công. Hầu hết thời gian đây chỉ là những gì bạn muốn. Các cách khác để thực thi lệnh từ ruby ​​không nên được sử dụng thường xuyên từ rake.
Gizmomogwai

4
một lợi ích khác của sh qua hệ thống: theo mặc định, sh có vẻ dài dòng hơn về các lệnh echo'ing.
Luke W

4
Tôi đã có thành công tốt nhất với việc sử dụng sh trong tệp rake thay vì hệ thống.
orkoden

3
Liên kết đã chết kể từ tháng 7 năm 2014, do RubyForge không còn nữa.
Arto Bendiken

1
@ArtoBendiken đã cập nhật nhận xét để bao gồm liên kết cập nhật tới rdoc.
Gizmomogwai

59

Có một số cách để thực thi các lệnh shell trong ruby. Một cách đơn giản (và có lẽ là phổ biến nhất) là sử dụng backticks:

task :hello do
  `echo "World!"`
end

Dấu gạch ngược có một hiệu ứng đẹp khi đầu ra tiêu chuẩn của lệnh shell trở thành giá trị trả về. Vì vậy, chẳng hạn, bạn có thể nhận được kết quả lsbằng cách

shell_dir_listing = `ls`

Nhưng có nhiều cách khác để gọi các lệnh shell và chúng đều có những lợi ích / nhược điểm và hoạt động khác nhau. Bài viết này giải thích các lựa chọn một cách chi tiết, nhưng đây là các khả năng tóm tắt nhanh:


Điều này sẽ ghi kết quả đầu ra vào bảng điều khiển, điều này có thể hơi khó hiểu. Trong rake, bạn có thể sử dụng sh "some_task"để có lệnh in đến thiết bị đầu cuối như thể bạn chạy nó trực tiếp từ thiết bị đầu cuối.
Automatico

7

Cho rằng sự đồng thuận dường như thích #shphương pháp của rake hơn , nhưng OP yêu cầu bash một cách rõ ràng, câu trả lời này có thể có một số công dụng.

Điều này có liên quan vì Rake#shsử dụng lệnh Kernel#systemgọi để chạy các lệnh shell. Ruby mã hóa cứng /bin/sh, bỏ qua trình bao được định cấu hình của người dùng hoặc $SHELLtrong môi trường.

Đây là một cách giải quyết mà gọi bash từ /bin/sh, cho phép bạn vẫn sử dụng shphương pháp:

task :hello_world do
  sh <<-EOS.strip_heredoc, {verbose: false}
    /bin/bash -xeu <<'BASH'
    echo "Hello, world!"
    BASH
  EOS
end

class String
  def strip_heredoc
    gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, ''.freeze)
  end
end

#strip_heredoc được mượn từ đường ray:

https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/string/strip.rb

Bạn có thể có được nó bằng cách yêu cầu active_support hoặc có thể nó được tự động tải khi bạn đang ở trong một dự án đường ray, nhưng tôi đang sử dụng đường ray bên ngoài này và vì vậy tôi phải tự gỡ bỏ nó.

Có hai heredocs, một bên ngoài với các điểm đánh dấu EOSvà một bên trong có các điểm đánh dấu BASH.

Cách hoạt động của điều này là bằng cách cho heredoc bên trong giữa các điểm đánh dấu BASH để bash stdin. Lưu ý rằng nó đang chạy trong ngữ cảnh của /bin/sh, vì vậy nó là một heredoc posix, không phải ruby. Thông thường, điều đó yêu cầu điểm đánh dấu kết thúc ở cột 1, điều này không xảy ra ở đây vì có thụt lề.

Tuy nhiên, vì nó được bọc bên trong một heredoc hồng ngọc, nên strip_heredocphương pháp được áp dụng ở đó sẽ khử lõm nó, đặt toàn bộ phần bên trái của heredoc bên trong vào cột 1 trước khi /bin/shnhìn thấy nó.

/bin/shcũng thường mở rộng các biến trong heredoc, điều này có thể ảnh hưởng đến tập lệnh. Các dấu nháy kép xung quanh điểm đánh dấu bắt đầu, 'BASH', yêu cầu /bin/shkhông mở rộng bất kỳ thứ gì bên trong heredoc trước khi nó được chuyển sang bash.

Tuy nhiên /bin/sh, vẫn áp dụng thoát cho chuỗi trước khi chuyển nó đến bash. Điều đó có nghĩa là các lần thoát dấu gạch chéo ngược phải được nhân đôi để vượt qua /bin/shthành bash, tức là \trở thành \\.

Các tùy chọn bash -xeulà tùy chọn.

Các -euđối số cho biết bash chạy ở chế độ nghiêm ngặt, chế độ này sẽ dừng thực thi khi có bất kỳ lỗi nào hoặc tham chiếu đến một biến không xác định. Điều này sẽ trả về một lỗi cho rake, điều này sẽ dừng tác vụ cào. Thông thường, đây là những gì bạn muốn. Các đối số có thể bị loại bỏ nếu bạn muốn hành vi bash bình thường.

Các -xtùy chọn để bash và {verbose: false}lập luận để #shlàm việc trong buổi hòa nhạc để cào mà chỉ in các lệnh bash mà đang thực sự thực hiện. Điều này hữu ích nếu tập lệnh bash của bạn không có nghĩa là chạy toàn bộ, chẳng hạn như nếu nó có một bài kiểm tra cho phép nó thoát sớm một cách duyên dáng trong tập lệnh.

Hãy cẩn thận không đặt mã thoát khác 0 nếu bạn không muốn nhiệm vụ cào thất bại. Thông thường, điều đó có nghĩa là bạn không muốn sử dụng bất kỳ || exitcấu trúc nào mà không đặt mã thoát rõ ràng, tức là || exit 0.


1
Cảm ơn. Điều này đã giúp tôi bằng cách đi đúng hướng. Tôi đang sử dụng nó trong một script ruby, nơi tôi cần gọi một số phương thức cần bash. Tôi cũng sử dụng một chuỗi dài với một số ký tự "lạ". Đây là cách tôi làm điều đó, có lẽ nó có thể giúp ích cho ai đó. system(%[xx=$(cat <<EOT\n#{body_text}\nEOT\n); bash <<BASH\n. #{ENV['HOME']}/.bash_profile; echo "$xx" | own_mail -TRm\nBASH\n)]. Tuy nhiên cách đơn giản nhất là nên đặt tất cả trong một kịch bản bash và chỉ gọi đó làsystem("path/my_bash_script.sh")
244an

5

%{echo "World!"}định nghĩa một chuỗi. Tôi mong bạn muốn %x{echo "World!"}.

%x{echo "World!"}thực hiện lệnh và trả về kết quả đầu ra (stdout). Bạn sẽ không thấy kết quả. Nhưng bạn có thể làm:

puts %x{echo "World!"}

Có nhiều cách khác để gọi một lệnh hệ thống:

  • Gậy ngược: `
  • system( cmd )
  • popen
  • Open3#popen3

1
Cần lưu ý rằng %xvà backticks thực sự đang gọi phương thức hạt nhân ruby ​​tương đương, chúng là cú pháp thay thế cho cùng một phương thức chính xác.
Ben Lee

1
system( cmd )đã làm cho tôi. Tôi đang cố gắng viết tập lệnh trình tạo pdf bằng wkhtmltopdf.
tsega

1

Có hai cách:

sh " expr "

hoặc là

%x( expr )

Lưu ý rằng (expr) có thể là {expr}, | expr | hoặc `expr '

Sự khác biệt là, sh "expr" là một phương thức ruby ​​để thực thi một cái gì đó và% x (expr) là phương thức tích hợp sẵn ruby. Kết quả và hành động là khác nhau. Đây là một ví dụ

task :default do
value = sh "echo hello"
puts value
value = %x(echo world)
puts value
end

được:

hello  # from sh "echo hello"
true   # from puts value
world  # from puts value

Bạn có thể thấy điều đó %x( expr )sẽ chỉ thực hiện shell expr nhưng stdout sẽ không hiển thị trên màn hình. Vì vậy, tốt hơn bạn nên sử dụng %x( expr )khi cần kết quả lệnh.

Nhưng nếu bạn chỉ muốn thực hiện một lệnh shell, tôi khuyên bạn nên sử dụng sh "expr". Bởi vì sh "irb"sẽ làm cho bạn đi vào vỏ irb, trong khi %x(irb)sẽ chết.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.