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

Прочитайте n строк за раз, используя Bash

Я прочитал страницу help read, но все еще не совсем понятен. Не знаю, какой вариант использовать.

Как я могу читать N строк за раз с помощью Bash?

4b9b3361

Ответ 1

Это сложнее, чем кажется. Проблема заключается в том, как сохранить дескриптор файла.

Решение состоит в создании нового дескриптора нового файла, который работает как stdin (дескриптор файла 0), но является независимым, а затем считывается из него по мере необходимости.

#!/bin/bash

# Create dummy input
for i in $(seq 1 10) ; do echo $i >> input-file.txt ; done

# Create new file handle 5
exec 5< input-file.txt

# Now you can use "<&5" to read from this file
while read line1 <&5 ; do
        read line2 <&5
        read line3 <&5
        read line4 <&5

        echo "Four lines: $line1 $line2 $line3 $line4"
done

# Close file handle 5
exec 5<&-

Ответ 2

Хотя выбранный ответ работает, на самом деле нет необходимости в отдельном дескрипторе файла. Просто использование команды чтения на оригинальном дескрипторе будет работать нормально.

Вот два примера, один со строкой, другой с файлом:

# Create a dummy file
echo -e "1\n2\n3\n4" > testfile.txt

# Loop through and read two lines at a time
while read -r ONE; do
    read -r TWO
    echo "ONE: $ONE TWO: $TWO"
done < testfile.txt

# Create a dummy variable
STR=$(echo -e "1\n2\n3\n4")

# Loop through and read two lines at a time
while read -r ONE; do
    read -r TWO
    echo "ONE: $ONE TWO: $TWO"
done <<< "$STR"

Запуск вышеприведенного сценария приведет к выводу (одинаковый вывод для обоих циклов):

ONE: 1 TWO: 2
ONE: 3 TWO: 4
ONE: 1 TWO: 2
ONE: 3 TWO: 4

Ответ 3

С Bash ≥4 вы можете использовать mapfile следующим образом:

while mapfile -t -n 10 ary && ((${#ary[@]})); do
    printf '%s\n' "${ary[@]}"
    printf -- '--- SNIP ---\n'
done < file

Чтобы читать по 10 строк за раз.

Ответ 4

Простейший метод - довольно понятный. Это похоже на метод, предоставляемый @Fmstrat, за исключением того, что второй оператор read находится перед do.

while read first_line; read second_line
do
    echo "$first_line" "$second_line"
done

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

seq 1 10 | while read first_line; read second_line 
do
    echo "$first_line" "$second_line"
done

выход:

1 2
3 4
5 6
7 8
9 10

Ответ 5

Я не думаю, что есть способ сделать это изначально в bash, но для этого можно создать удобную функцию:

#
# Reads N lines from input, keeping further lines in the input.
#
# Arguments:
#   $1: number N of lines to read.
#
# Return code:
#   0 if at least one line was read.
#   1 if input is empty.
#
function readlines () {
    local N="$1"
    local line
    local rc="1"

    # Read at most N lines
    for i in $(seq 1 $N)
    do
        # Try reading a single line
        read line
        if [ $? -eq 0 ]
        then
            # Output line
            echo $line
            rc="0"
        else
            break
        fi
    done

    # Return 1 if no lines where read
    return $rc
}

С помощью этого можно легко выполнить цикл над N-строчными фрагментами данных, выполнив что-то вроде

while chunk=$(readlines 10)
do
    echo "$chunk" | ... # Whatever processing
done

В этом цикле $chunk будет содержать 10 строк ввода на каждой итерации, кроме последней, которая будет содержать последние строки ввода, которые могут быть меньше 10, но всегда больше 0.

Ответ 6

Это намного проще! :)

cat input-file.txt | xargs -L 10 ./do_something.sh

или

cat input-file.txt | xargs -L 10 echo

Ответ 7

Я придумал что-то очень похожее на ответ @albarji, но более краткий.

read_n() { for i in $(seq $1); do read || return; echo $REPLY; done; }

while lines="$(read_n 5)"; do
    echo "========= 5 lines below ============"
    echo "$lines"
done < input-file.txt

Функция read_n будет читать строки $1 из stdin (используйте перенаправление, чтобы прочитать ее из файла, как и встроенная команда read). Поскольку код выхода из read поддерживается, вы можете использовать read_n в цикле, как показано вышеприведенным примером.

Ответ 8

Просто используйте цикл for:

for i in $(seq 1 $N) ; do read line ; lines+=$line$'\n' ; done

В bash версии 4 вы также можете использовать команду mapfile.

Ответ 9

В зависимости от того, что вы пытаетесь сделать, вы можете просто сохранить предыдущие строки.

LINE_COUNT=0
PREVLINE1=""
PREVLINE2=""
while read LINE
  do LINE_COUNT=$(($LINE_COUNT+1));
    if [[ $LINE_COUNT == 3 ]]; then
       LINE_COUNT=0
       # do whatever you want to do with the 3 lines
    done
    PREVLINE2="$PREVLINE1"
    PREVLINE1="$LINE"
  done
done < $FILE_IN

Ответ 10

Я знаю, что вы спросили о bash, но я поражен тем, что это работает с zsh

#!/usr/bin/env zsh    
cat 3-lines.txt | read -d\4 my_var my_other_var my_third_var

К сожалению, это не работает с bash, по крайней мере, теми версиями, которые я пробовал.

"magic" здесь - это -d\4 (это не работает в bash), который устанавливает разделитель строк как символ EOT, который будет найден в конце вашего cat, или любую команду, которая производит вывод.

Если вы хотите прочитать массив элементов N, bash имеет readarray и mapfile, который может читать файлы с линиями N и сохранять каждую строку в одной позиции массива.

ИЗМЕНИТЬ

После некоторых попыток я узнал, что это работает с bash:

$ read -d# a b
Hello
World
#
$ echo $a $b
Hello World
$

Однако я не мог заставить { cat /tmp/file ; echo '#'; } | read -d# a b работать: (

Ответ 11

echo имитирует файл с двумя строками ввода, используйте head -2 до paste при необходимости:

IFS=\; read A B < <(echo -en "X1 X2\nY1 Y2\n" | paste -s -d\;)

Если вы хотите читать строки в цикле и создавать пары и строки, используйте только одно слово:

while read NAME VALUE; do 
    echo "$NAME=$VALUE"; 
done < <(echo -en "M\n1\nN\n2\nO\n3\n" | xargs -L2 echo)

Ответ 12

Вот альтернативный способ сделать это:

//This will open the file and users can start adding variables.
cat > file
//After finished ctrl + D will close it
cat file|while read line;
do
  //do some stuff here
done

Ответ 14

mapfile -n читает дополнительную строку за последней строкой, назначенной массиву, через Bash. Исправлено в 4.2.35

Ответ 15

Посмотрев на все ответы, я думаю, что следующее является самым простым, то есть больше сценаристов поймут это лучше, чем любое другое решение, но только для небольшого числа элементов:

while read -r var1 && read -r var2; do 
    echo "$var1" "$var2"
done < yourfile.txt

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

while read -r var1; read -r var2; do 
    echo "$var1" "$var2"
done < yourfile.txt

Преимущество заключается в том, что вам не нужны продолжения строки для большего количества элементов:

while 
    read -r var1
    read -r var2
    ...
    read -r varN
do 
    echo "$var1" "$var2"
done < yourfile.txt

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

while read -r var1 var2; do 
    echo "$var1" "$var2"
done <<< $(cat yourfile.txt | xargs -L 2 )

но опять же это использует менее известный оператор <<<. Однако этот подход имеет то преимущество, что если ваш сценарий изначально

while read -r var1; do 
    echo "$var1"
done <<< yourfile.txt

тогда расширение его на несколько строк несколько естественно:

while read -r var1 var2; do 
    echo "$var1" "$var2"
done <<< $(cat endpoints.txt | xargs -L 2 )

Простое решение

while read -r var1; do
    read -r var2
    echo "$var1" "$var2"
done < yourfile.txt

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