แนวทางปฏิบัติที่ดีที่สุดกับ STDIN ใน Ruby?


307

ฉันต้องการจัดการกับอินพุตบรรทัดคำสั่งใน Ruby:

> cat input.txt | myprog.rb
> myprog.rb < input.txt
> myprog.rb arg1 arg2 arg3 ...

วิธีที่ดีที่สุดที่จะทำคืออะไร? โดยเฉพาะอย่างยิ่งฉันต้องการจัดการกับ STDIN ที่ว่างเปล่าและฉันหวังว่าจะได้โซลูชันที่สง่างาม

#!/usr/bin/env ruby

STDIN.read.split("\n").each do |a|
   puts a
end

ARGV.each do |b|
    puts b
end

5
เพียงหมายเหตุเล็กน้อย: สองบรรทัดแรกของคำสั่งที่คุณให้นั้นเหมือนกันทุกประการจากมุมมองของmyprog.rb: input.txtไฟล์แนบกับstdin ; เชลล์จัดการสิ่งนี้ให้คุณ
เหม่ย

6
^^ สิ่งนี้มักจะถูกเรียกว่า "การใช้แมวที่ไร้ประโยชน์" คุณจะเห็นว่ามันเยอะมาก
Steve Kehlet

18
@SteveKehlet แต่ฉันเชื่อว่ามันฉลาดกว่าที่เรียกว่า "การทารุณกรรมแมว"
OneChillDude

คำตอบ:


403

ต่อไปนี้เป็นบางสิ่งที่ฉันพบในคอลเล็กชันทับทิมที่ไม่ชัดเจน

ดังนั้นใน Ruby การใช้คำสั่ง Unix แบบไม่มีเบลล์อย่างง่ายcatจะเป็น:

#!/usr/bin/env ruby
puts ARGF.read

ARGFคือเพื่อนของคุณเมื่อพูดถึงอินพุต เป็นไฟล์เสมือนที่รับอินพุตทั้งหมดจากไฟล์ที่ระบุชื่อหรือทั้งหมดจาก STDIN

ARGF.each_with_index do |line, idx|
    print ARGF.filename, ":", idx, ";", line
end

# print all the lines in every file passed via command line that contains login
ARGF.each do |line|
    puts line if line =~ /login/
end

ขอบคุณพระเจ้าที่เราไม่ได้รับผู้ประกอบการเพชรในทับทิม แต่เราได้รับARGFการแทนที่ แม้ว่าจะคลุมเครือจริง ๆ แล้วกลายเป็นประโยชน์ พิจารณาโปรแกรมนี้ซึ่งประกอบส่วนหัวลิขสิทธิ์ไว้ล่วงหน้า (ขอบคุณ Perlism อื่น-i) ต่อไฟล์ทุกไฟล์ที่กล่าวถึงในบรรทัดคำสั่ง:

#!/usr/bin/env ruby -i

Header = DATA.read

ARGF.each_line do |e|
  puts Header if ARGF.pos - e.length == 0
  puts e
end

__END__
#--
# Copyright (C) 2007 Fancypants, Inc.
#++

เครดิตไปที่:


12
ARGF เป็นวิธีที่จะไป มันเป็นของ Ruby ที่สร้างขึ้นเพื่อจัดการกับไฟล์และ stdin ในแบบรอบด้าน
Pistos

1
(เห็นสิ่งนี้และความคิดของคุณ) เป็นเครดิตเหล่านั้นอีกครั้ง: blog.nicksieger.com/articles/2007/10/06/…
Deau

นั่นเป็นสิ่งที่ดีมาก วันของฉันจะเสร็จสมบูรณ์หากมีรูปแบบที่ดีในการจำลองวิธีการทำงานของ AWK :-)
จะ

บางทีควรทราบว่าidxจะเป็น "หมายเลขบรรทัด" ในไฟล์เสมือนที่เชื่อมต่ออินพุตทั้งหมดแทนที่จะเป็นหมายเลขบรรทัดสำหรับแต่ละไฟล์
Alec Jacobson

หมายเหตุ#!/usr/bin/env ruby -iบรรทัดนี้ใช้ไม่ได้กับ Linux: stackoverflow.com/q/4303128/735926
bfontaine

43

Ruby ให้วิธีอื่นในการจัดการ STDIN: แฟล็ก -n มันถือว่าโปรแกรมทั้งหมดของคุณเป็นภายในวงมากกว่า STDIN (รวมถึงไฟล์ที่ส่งผ่านเป็นบรรทัดคำสั่ง args) ดูเช่นสคริปต์ 1 บรรทัดต่อไปนี้:

#!/usr/bin/env ruby -n

#example.rb

puts "hello: #{$_}" #prepend 'hello:' to each line from STDIN

#these will all work:
# ./example.rb < input.txt
# cat input.txt | ./example.rb
# ./example.rb input.txt

8
shebang สามส่วน#!/usr/bin/env ruby -nจะไม่ทำงานเนื่องจาก "ruby -n" จะถูกส่งผ่านไปยัง / usr / bin / env เป็นอาร์กิวเมนต์เท่านั้น ดูคำตอบนี้สำหรับรายละเอียดเพิ่มเติม สคริปต์จะทำงานหากทำงานruby -n script.rbอย่างชัดเจน
artm

5
@jdizzle: มันทำงานใน OSX แต่ไม่ได้อยู่ในลินุกซ์ - และที่ว่าปัญหา: มันไม่ได้เป็นแบบพกพา
mklement0

32

ฉันไม่แน่ใจว่าคุณต้องการอะไร แต่ฉันจะใช้สิ่งนี้:

#!/usr/bin/env ruby

until ARGV.empty? do
  puts "From arguments: #{ARGV.shift}"
end

while a = gets
  puts "From stdin: #{a}"
end

โปรดทราบว่าเนื่องจากอาร์เรย์ ARGV ว่างเปล่าก่อนหน้าgetsนี้ทับทิมจะไม่พยายามตีความอาร์กิวเมนต์เป็นไฟล์ข้อความที่จะอ่าน (พฤติกรรมที่สืบทอดมาจาก Perl)

หาก stdin ว่างเปล่าหรือไม่มีข้อโต้แย้งจะไม่มีการพิมพ์อะไรเลย

กรณีทดสอบน้อย:

$ cat input.txt | ./myprog.rb
From stdin: line 1
From stdin: line 2

$ ./myprog.rb arg1 arg2 arg3
From arguments: arg1
From arguments: arg2
From arguments: arg3
hi!
From stdin: hi!

18

บางทีสิ่งนี้บางที

#/usr/bin/env ruby

if $stdin.tty?
  ARGV.each do |file|
    puts "do something with this file: #{file}"
  end
else
  $stdin.each_line do |line|
    puts "do something with this line: #{line}"
  end
end

ตัวอย่าง:

> cat input.txt | ./myprog.rb
do something with this line: this
do something with this line: is
do something with this line: a
do something with this line: test
> ./myprog.rb < input.txt 
do something with this line: this
do something with this line: is
do something with this line: a
do something with this line: test
> ./myprog.rb arg1 arg2 arg3
do something with this file: arg1
do something with this file: arg2
do something with this file: arg3

stdin ไม่จำเป็นต้องเป็นข้อความ Notorius ไม่ใช่ข้อความเป็นตัวอย่างของการบีบอัด / uncompress บางอย่าง (each_line เป็นเพียงการเตรียมการสำหรับ ascii เท่านั้น) each_byte อาจจะ?
Jonke

12
while STDIN.gets
  puts $_
end

while ARGF.gets
  puts $_
end

นี่คือแรงบันดาลใจจาก Perl:

while(<STDIN>){
  print "$_\n"
}

4
ใช่เลยสำหรับความเรียบง่ายและการอ่านง่าย! โอ้ไม่เดี๋ยวก่อนนั่นคือ '$ _'? โปรดใช้ภาษาอังกฤษใน Stack Overflow!


1

ฉันจะเพิ่มว่าเพื่อที่จะใช้ARGFกับพารามิเตอร์ที่คุณจะต้องล้างก่อนที่จะเรียกARGV ARGF.eachนี่เป็นเพราะARGFจะปฏิบัติต่อสิ่งใดในARGVชื่อไฟล์และอ่านบรรทัดจากที่นั่นก่อน

นี่คือตัวอย่างการใช้งาน 'tee':

File.open(ARGV[0], 'w') do |file|
  ARGV.clear

  ARGF.each do |line|
    puts line
    file.write(line)
  end
end


0

ดูเหมือนว่าคำตอบส่วนใหญ่สมมติว่าอาร์กิวเมนต์เป็นชื่อไฟล์ที่มีเนื้อหาที่จะ catd ไปยัง stdin ด้านล่างทุกอย่างถือว่าเป็นเพียงข้อโต้แย้ง ถ้า STDIN มาจาก TTY จะถูกละเว้น

$ cat tstarg.rb

while a=(ARGV.shift or (!STDIN.tty? and STDIN.gets) )
  puts a
end

อาร์กิวเมนต์หรือ stdin อาจว่างเปล่าหรือมีข้อมูล

$ cat numbers 
1
2
3
4
5
$ ./tstarg.rb a b c < numbers
a
b
c
1
2
3
4
5
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.