1

Я хочу разбить и GZip большой файл, и этот ответ, казалось, был тем, что я искал, и это казалось очень полезным способом делать вещи, о которых я никогда не думал, поэтому я хотел бы обобщить это; Единственная проблема: это не работает.

Скажем, я хочу разделить свой ввод и обработать его дальше (я знаю split но я хочу напрямую передать его в свой сценарий!)

Это использует read чтобы прочитать строку в переменную

#!/bin/bash
printf " a \n b \n c \n d " |
for ((i = 0 ; i < 2 ; i++)) ; do
  echo "<< $i >>"
  for ((j = 0 ; j < 2 ; j++)) ; do
    read l
    echo "$l"
  done
done

Это печатает

<< 0 >>
a
b
<< 1 >>
c
d

Что почти то, что я хочу, кроме того факта, что он обрезает пробелы от начала и до конца (и, возможно, изменяет строку другими способами? Будет ли он работать с произвольным содержимым в кодировке UTF-8?) редактирование решено

И я думаю, что это может быть довольно медленно. редактировать протестированные его: по крайней мере 3000X медленнее.

Поэтому я попытался передать это через head (я получил результат с использованием awk как следует из ответа, похоже, что он ничего не делает по-другому)

#!/bin/bash
printf " a \n b \n c \n d " |
for ((i = 0 ; i < 2 ; i++)) ; do
  echo "<< $i >>"
  head -n 2
done

Это печатает

<< 0 >>
 a 
 b 
<< 1 >>

И останавливается, потому что head закрывает вход при выходе. Я не нашел программу, которая этого не делает, и, возможно, она действительно поддерживается системой? (Я на OS X)

Использование head -n 2 <&0 которое (в соответствии с документацией bash) копирует дескриптор файла первым, также не работает.

Должен ли я использовать именованную трубу? Есть ли какое-то заклинание, чтобы заставить это работать?

3 ответа3

1

Проблема здесь не совсем в том, что head или awk "закрывают вход". У них нет выбора; любая программа закрывает свои входные данные после завершения, и это обеспечивается операционной системой.

Проблема в том, что стандартный ввод - это канал, а программы выполняют буферизованные чтения. Невозможно извлечь из канала непрочитанные данные, поэтому все данные, находящиеся в режиме чтения, пропали. Если вместо использования канала вы используете файл, вы, вероятно, увидите, что он работает нормально:

#!/bin/bash
printf " %s \n" a b c d > /tmp/abcd
for ((i = 0 ; i < 2 ; i++)) ; do
    echo "<< $i >>"
    for ((j = 0 ; j < 2 ; j++)) ; do
        read
        echo "$REPLY"
    done
done < /tmp/abcd

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

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv) {
  int n = 1000;
  if (argc > 1) n = atoi(argv[1]);
  setvbuf(stdin, NULL, _IONBF, 0);
  for (int ch = getchar(); ch != EOF; ch = getchar()) {
    putchar(ch);
    if (ch == '\n' && --n <= 0) break;
  }
  return n > 0;
}

Это хорошо сработало для меня (опять же в Ubuntu - и вам нужно скомпилировать его с помощью -std=c99 или -std=c11 чтобы компилятор не жаловался). Это правда, что программа не вызывает fclose(stdin) , но добавление не будет иметь никакого значения. С другой стороны, удаление вызова setvbuf , вероятно, вернет вас к симптому, который вы наблюдали с head . (И это также заставит программу работать намного быстрее.)

Если бы у вас была версия GNU split вместо версии BSD, которая поставляется с OS X, вы могли бы использовать полезный синтаксис --filter=COMMAND который довольно хорошо выполняет именно то, что вы хотите; вместо создания разделенных файлов он передает каждую секцию файла в вызов указанной КОМАНДЫ (и устанавливает переменную среды $FILE в ожидаемое имя файла).

1

Указывая переменную для read вы заказываете ее для разделения слов. Не делайте этого, и пробелы останутся нетронутыми

#!/bin/bash
printf " a \n b \n c \n d " |
for ((i = 0 ; i < 2 ; i++)) ; do
    echo "<< $i >>"
    for ((j = 0 ; j < 2 ; j++)) ; do
        read
        echo "$REPLY"
    done
done

Выход:

<< 0 >>
 a  
 b  
<< 1 >>
 c  
 d  

Это кажется очень простым, но на самом деле вы задали очень хороший вопрос, так как эта особенность не объясняется в человеке четко.

PS Я бы также использовал флаг -r (не рассматривайте \ как escape-символ) для read .

0

Но если вы хотите написать автономный скрипт для работы с большими файлами, AWK будет гораздо более подходящим, чем Bash, из соображений эффективности. Однострочник:

$ awk 'NR%2 { print "<< " int(NR/2) " >>" }; 1' <<< $' a \n b \n c \n d '
<< 0 >>
 a 
 b 
<< 1 >>
 c 
 d 

Так же, как скрипт:

#!/usr/bin/awk -f

# where (number of line) mod 2 == 1, i. e. every odd line
NR%2 == 1 {
    # print (number of line) div 2
    print "<< " int(NR/2) " >>"
}

{  
    # print input stream
    print
} 

То же самое, что и Bash-скрипт:

#!/bin/bash

while read; do
    let lnum++
    ((lnum % 2 == 1)) && \
        echo "<< $((lnum / 2)) >>"
    echo "$REPLY"
done

Тест с одним миллионом строк:

$ awk 'BEGIN { for (i=1; i<=10^6; i++) print i }' >> 1e6

$ time ./pascal.awk < 1e6 > /dev/null

real    0m0.663s
user    0m0.656s
sys     0m0.004s

$ time ./pascal.sh < 1e6 > /dev/null

real    0m31.293s
user    0m29.410s
sys     0m1.852s

Вы видите, почему Bash не является предпочтительным интерпретатором здесь.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .