Подтвердить что ты не робот

Линейная потоковая передача в Ruby (например, grep)

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

Как мне это сделать? Кажется, что у Python такой же недостаток.

4b9b3361

Ответ 1

Похоже, что лучше всего использовать 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 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, этот текст будет потерян. Я считаю, что если вы замените catch следующим образом, он должен исправить проблему:

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

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

Ответ 2

Вы всегда можете включить автозапуск в любом потоке, который вы хотите:

STDOUT.sync = true

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

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

Ответ 3

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

Ответ 4

Принятый ответ 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

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