Линейно-ориентированная потоковая передача в Ruby (например, grep) - PullRequest
12 голосов
/ 03 августа 2011

По умолчанию Ruby открывает $stdin и $stdout в режиме с буферизацией.Это означает, что вы не можете использовать Ruby для выполнения подобной операции grep фильтрации текста.Есть ли способ заставить Ruby использовать линейно-ориентированный режим?Я видел различные решения, включая popen3 (который работает только в режиме с буферизацией) и pty (который отдельно не обрабатывает $stdout и $stderr, которые мне требуются).

КакЯ сделаю это?У Python такой же недостаток.

Ответы [ 4 ]

4 голосов
/ 11 августа 2011

Похоже, что вам лучше всего использовать STDOUT.syswrite и STDOUT.sysread. Казалось, что следующее имеет довольно хорошую производительность, несмотря на то, что код выглядит ужасно:

STDIN.sync = true
STDOUT.syswrite "Looking for #{ARGV[0]}\n"

def next_line
  mybuff = @overflow || ""
  until mybuff[/\n/]
    mybuff += STDIN.sysread(8)
  end
  overflow = mybuff.split("\n")
  out, *others = overflow
  @overflow = others.join("\n")
  out
rescue EOFError => e
  false  # NB: There's a bug here, see below
end

line = next_line
while line
  STDOUT.syswrite "#{line}\n" if line =~ /#{ARGV[0]}/i
  line = next_line
end

Примечание: Не уверен, что вам нужен #sync с #sysread, но если это так, вам, вероятно, следует также синхронизировать STDOUT. Кроме того, он считывает 8 байтов за раз в mybuff - вы должны поэкспериментировать с этим значением, оно крайне неэффективно / сильно загружает процессор. Наконец, этот код является хакерским и нуждается в рефакторинге, но он работает - протестировал его, используя ls -l ~/* | ruby rgrep.rb doc (где 'doc' - поисковый термин)


Второе замечание: по-видимому, я был так занят, пытаясь заставить его работать хорошо, я не смог заставить его работать правильно! Поскольку у Дмитрия Шевкопляса отмечено , если в @overflow есть текст, когда возникает EOFError, этот текст будет потерян. Я полагаю, что если вы замените защелку на следующую, это должно решить проблему:

rescue EOFError => e
  return false unless @overflow && @overflow.length > 0
  output = @overflow
  @overflow = ""
  output
end

(если вы сочли это полезным, пожалуйста, проголосуйте за Дмитрия ответ !)

4 голосов
/ 03 августа 2011

Вы всегда можете включить автозапуск для любого нужного вам потока:

STDOUT.sync = true

Это приведет к немедленной фиксации любых записей.

У большинства языков есть эта функция, но они всегда называют это немного по-другому.

2 голосов
/ 15 октября 2016

Принятый ответ user208769 хорош, но имеет один недостаток: при определенных условиях вы потеряете последнюю строку. Ниже я покажу, как воспроизвести и как это исправить:

Чтобы воспроизвести ошибку «последняя строка потеряна»:

mkdir deleteme
touch deleteme/1 deleteme/2 deleteme/3
ls deleteme/ | ./rgrep.rb ''
Looking for
1
2

как вы видите, файл "3" отсутствует в выводе rgrep. Удивительно, но для другой длины имени файла это будет работать по-другому, хотя! Посмотрите:

rm -fr deleteme/
mkdir deleteme
touch deleteme/11 deleteme/22 deleteme/33
ls deleteme/ | ./rgrep.rb ''
Looking for
11
22
33

Теперь третий файл присутствует! Что за ошибка! Разве она не красавица !!?
Можно только представить, какой ущерб может нанести это случайное поведение.

Чтобы исправить ошибку, мы бы немного изменили спасательную часть:

#!/usr/bin/env ruby
STDIN.sync = true
STDOUT.syswrite "Looking for #{ARGV[0]}\n"

def next_line
  mybuff = @overflow || ""
  until mybuff[/\n/]
    mybuff += STDIN.sysread(8)
  end
  overflow = mybuff.split("\n")
  out, *others = overflow
  @overflow = others.join("\n")
  out
rescue EOFError => e
  if @overflow.to_s.size > 0
    leftover_line = @overflow
    @overflow = ''
    return leftover_line
  else
    false
  end
end

line = next_line
while line
  STDOUT.syswrite "#{line}\n" if line =~ /#{ARGV[0]}/i
  line = next_line
end

Я оставлю часть «почему» из этого поста в качестве упражнения для любопытных, так как в противном случае она не будет переварена должным образом (и этот пост уже слишком длинный для моего первого поста;) хех ...

2 голосов
/ 10 августа 2011

Вы можете позвонить $stdout.flush после того, как напечатали свою строку, и позвонить $stdin.readline, чтобы получить одну строку.

...