Читайте файл построчно, присваивая значение переменной


754

У меня есть следующий файл .txt:

Marco
Paolo
Antonio

Я хочу читать это построчно, и для каждой строки я хочу присвоить строковое значение .txt переменной. Предположим, моя переменная $name, поток:

  • Читать первую строку из файла
  • Назначить $name= "Марко"
  • Выполните несколько задач с $name
  • Читать вторую строку из файла
  • Назначить $name= "Паоло"


3
Можно ли как-то объединить эти вопросы? У обоих есть несколько действительно хороших ответов, которые подчеркивают различные аспекты проблемы, плохие ответы содержат подробные объяснения в комментариях о том, что в них плохого, и на данный момент вы не можете получить полный обзор того, что следует учитывать, из ответов один единственный вопрос от пары. Было бы полезно иметь все это в одном месте, а не на 2 страницы.
Егор Ганс

Ответы:


1359

Следующее читает файл, передаваемый в качестве аргумента построчно:

while IFS= read -r line; do
    echo "Text read from file: $line"
done < my_filename.txt

Это стандартная форма для чтения строк из файла в цикле. Объяснение:

  • IFS=(или IFS='') предотвращает обрезку начальных / конечных пробелов.
  • -r предотвращает интерпретацию обратной косой черты.

Или вы можете поместить его в вспомогательный скрипт bash, пример содержимого:

#!/bin/bash
while IFS= read -r line; do
    echo "Text read from file: $line"
done < "$1"

Если вышеперечисленное сохраняется в сценарии с именем файла readfile, его можно запустить следующим образом:

chmod +x readfile
./readfile filename.txt

Если файл не является стандартным текстовым файлом POSIX (= не завершается символом новой строки), цикл может быть изменен для обработки завершающих частичных строк:

while IFS= read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "$1"

Здесь, || [[ -n $line ]]предотвращает игнорирование последней строки, если она не заканчивается на \n(поскольку readвозвращает ненулевой код завершения, когда она встречает EOF).

Если команды внутри цикла также считываются из стандартного ввода, используемый дескриптор файла readможет быть заменен чем-то другим (избегайте стандартных дескрипторов файлов ), например:

while IFS= read -r -u3 line; do
    echo "Text read from file: $line"
done 3< "$1"

(Оболочки не Bash могут не знать read -u3; используйте read <&3вместо этого.)


23
Есть предостережение с этим методом. Если что-то внутри цикла while является интерактивным (например, читает из stdin), то его вход будет принимать от $ 1. Вам не будет предоставлена ​​возможность вводить данные вручную.
Карпи

10
Примечательно - некоторые команды нарушают (как, например, они нарушают цикл) это. Например, sshбез -nфлага будет эффективно заставить вас выйти из цикла. Вероятно, для этого есть веская причина, но мне потребовалось некоторое время, чтобы понять, что приводило к сбою моего кода, прежде чем я это обнаружил.
Алекс

6
как однострочник: тогда как IFS = '' read -r line || [[-n "$ line"]]; сделать эхо "$ line"; сделано <имя файла
Джозеф Джонсон

8
@ OndraŽižka, это вызвано ffmpegпотреблением stdin. Добавьте </dev/nullк своей ffmpegстроке, и он не сможет, или использовать альтернативный FD для цикла. Этот подход «альтернативного FD» выглядит следующим образом while IFS='' read -r line <&3 || [[ -n "$line" ]]; do ...; done 3<"$1".
Чарльз Даффи

9
ворчание : советовать .shпродление. Исполняемые файлы в UNIX обычно вообще не имеют расширений (вы не запускаете ls.elf), и наличие bash-shebang (и инструментов только для bash, таких как [[ ]]) и расширение, подразумевающее совместимость с POSIX sh, внутренне противоречиво.
Чарльз Даффи

309

Я призываю вас использовать -rфлаг, readкоторый обозначает:

-r  Do not treat a backslash character in any special way. Consider each
    backslash to be part of the input line.

Я цитирую man 1 read.

Другое дело - взять имя файла в качестве аргумента.

Вот обновленный код:

#!/usr/bin/bash
filename="$1"
while read -r line; do
    name="$line"
    echo "Name read from file - $name"
done < "$filename"

4
Обрезает начальные и конечные пробелы от линии
barfuin

@ Томас, а что происходит с пробелами посередине? Подсказка: нежелательная попытка выполнения команды.
kmarsh

1
Это сработало для меня, в отличие от принятого ответа.
Нейротрансмиттер

3
@TranslucentCloud, если это сработало, а принятый ответ - нет, я подозреваю, что ваша оболочка была sh, нет bash; расширенная тестовая команда, используемая в || [[ -n "$line" ]]синтаксисе в принятом ответе, является башизмом. Тем не менее, этот синтаксис на самом деле имеет соответствующее значение: он заставляет цикл продолжаться до последней строки во входном файле, даже если он не имеет новой строки. Если вы хотите сделать это в POSIX-совместимом виде, вам нужно || [ -n "$line" ]использовать [вместо [[.
Чарльз Даффи

3
Тем не менее, это все еще необходимо изменить, чтобы установить IFS=для readпредотвращения обрезки пробелов.
Чарльз Даффи

132

Использование следующего шаблона Bash позволит вам читать по одному значению за раз из файла и обрабатывать его.

while read name; do
    # Do what you want to $name
done < filename

14
как однострочник: пока читаешь имя; сделать эхо $ {имя}; сделано <имя файла
Джозеф Джонсон

4
@CalculusKnight, это только «сработало», потому что вы не использовали достаточно интересные данные для тестирования. Попробуйте контент с обратной косой чертой или строкой, содержащей только *.
Чарльз Даффи

7
@Matthias, предположения, которые в конечном итоге оказываются ложными, являются одним из крупнейших источников ошибок, как влияющих на безопасность, так и других. Крупнейшее событие потери данных, которое я когда-либо видел, было связано со сценарием, который, как предполагалось, «буквально никогда не возникнет» - переполнением буфера, сбрасывающим случайную память в буфер, используемый для имен файлов, вызывая сценарий, делающий предположения о том, какие имена могут когда-либо существовать. имеют очень, очень неудачное поведение.
Чарльз Даффи

5
@Matthias, ... и это особенно верно здесь, поскольку примеры кода, показанные в StackOverflow, предназначены для использования в качестве обучающих инструментов, чтобы люди могли повторно использовать шаблоны в своей работе!
Чарльз Даффи

5
@Matthias, я совершенно не согласен с утверждением, что «вы должны разрабатывать свой код только для данных, которые вы ожидаете». Неожиданные случаи, когда ваши ошибки, где ваши уязвимости безопасности - обработка их - это разница между кодом slapdash и надежным кодом. Конечно, эта обработка не обязательно должна быть причудливой - это может быть просто «выход с ошибкой» - но если у вас вообще нет обработки, то ваше поведение в неожиданных случаях не определено.
Чарльз Даффи

76
#! /bin/bash
cat filename | while read LINE; do
    echo $LINE
done

8
Ничего не имею против других ответов, возможно, они более изощренные, но я одобряю этот ответ, потому что он прост, читабелен и достаточен для того, что мне нужно. Обратите внимание, что для того, чтобы он работал, текстовый файл, который нужно прочитать, должен заканчиваться пустой строкой (то есть нужно нажимать Enterпосле последней строки), в противном случае последняя строка будет игнорироваться. По крайней мере, так случилось со мной.
Антонио Виниций Менезес Медей

12
Бесполезное использование кошки, не так ли?
Брайан Агнью

5
И цитата не работает; и вы не должны использовать имена переменных в верхнем регистре, потому что они зарезервированы для системного использования.
tripleee

7
@AntonioViniciusMenezesMedei, ... кроме того, я видел, как люди несут финансовые потери, потому что они предполагали, что эти предостережения никогда не будут иметь для них значения; не выучил хорошие практики; а затем следовал привычкам, к которым они привыкли при написании сценариев, управляющих резервным копированием критических данных выставления счетов. Важно научиться правильно делать вещи.
Чарльз Даффи

6
Другая проблема заключается в том, что канал открывает новый подоболочек, то есть все переменные, установленные внутри цикла, не могут быть прочитаны после завершения цикла.
mxmlnkn

20

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

#!/bin/bash
#
# This program reads lines from a file.
#

end_of_file=0
while [[ $end_of_file == 0 ]]; do
  read -r line
  # the last exit status is the 
  # flag of the end of file
  end_of_file=$?
  echo $line
done < "$1"

20

Использование:

filename=$1
IFS=$'\n'
for next in `cat $filename`; do
    echo "$next read from $filename" 
done
exit 0

Если вы установили IFSиначе, вы получите странные результаты.


34
Это ужасный метод . Пожалуйста, не используйте его, если только вы не хотите иметь проблемы с тряпкой, которые будут иметь место, прежде чем вы это поймете!
gniourf_gniourf

13
@MUYBelgium вы пробовали с файлом, который содержит один *в строке? Во всяком случае, это антипаттерн . Не читайте строки с for .
gniourf_gniourf

2
@ OndraŽižka, readподход является подходом наилучшей практики на основе консенсуса сообщества . Предупреждение, которое вы упоминаете в своем комментарии, применяется, когда ваш цикл запускает команды (такие как ffmpeg), которые читают из stdin, тривиально решается с использованием не-stdin FD для цикла или перенаправляет ввод таких команд. Напротив, работа с ошибкой forглобализации в вашем подходе -loop означает внесение (а затем и необходимость обратного изменения) глобальных настроек оболочки.
Чарльз Даффи

1
@ OndraŽižka, ... кроме того, forподход петли используется здесь , означает , что все содержание должно быть прочитано до того , как цикл может начать выполнение на все, что делает его совершенно непригодным для использования , если вы цикл над гигабайтами данных , даже если у вас есть инвалиды подстановка; while readциклу не нужно не больше , чем данные в одной строке в магазин за один раз, то есть он может начать выполнение в то время как содержание генерации подпроцесса все еще работает (таким образом , быть пригодным для целей потоковой передачи), а также имеет ограниченный объем памяти.
Чарльз Даффи

1
На самом деле, даже у whileоснованных на подходах проблем, похоже, есть * характерные. Смотрите комментарии принятого ответа выше. Не спорю с тем, что for-iteration над файлами является антипаттерном.
Егор Ганс

9

Если вам нужно обработать как входной файл, так и пользовательский ввод (или что-либо еще из stdin), используйте следующее решение:

#!/bin/bash
exec 3<"$1"
while IFS='' read -r -u 3 line || [[ -n "$line" ]]; do
    read -p "> $line (Press Enter to continue)"
done

На основании принятого ответа и учебника перенаправления bash-хакеров .

Здесь мы открываем файловый дескриптор 3 для файла, переданного в качестве аргумента сценария, и говорим readиспользовать этот дескриптор в качестве input ( -u 3). Таким образом, мы оставляем дескриптор ввода по умолчанию (0), прикрепленный к терминалу или другому источнику ввода, способный считывать ввод данных пользователем.


7

Для правильной обработки ошибок:

#!/bin/bash

set -Ee    
trap "echo error" EXIT    
test -e ${FILENAME} || exit
while read -r line
do
    echo ${line}
done < ${FILENAME}

Не могли бы вы добавить объяснение?
Тайлер Кристиан

К сожалению, он пропускает последнюю строку в файле.
ungalcrys

... а также, из-за отсутствия цитирования, строки мунгов, содержащие подстановочные знаки - как описано в BashPitfalls # 14 .
Чарльз Даффи

0

Следующее просто распечатает содержимое файла:

cat $Path/FileName.txt

while read line;
do
echo $line     
done

1
Этот ответ действительно ничего не добавляет к существующим, не работает из-за опечатки / ошибки и ломается во многих отношениях.
Конрад Рудольф

0

Использовать инструмент IFS (внутренний разделитель полей) в bash, определяет символ, используемый для разделения строк на токены, по умолчанию включает в себя < tab > / < space > / < newLine >

Шаг 1 : Загрузите данные файла и вставьте в список:

# declaring array list and index iterator
declare -a array=()
i=0

# reading file in row mode, insert each line into array
while IFS= read -r line; do
    array[i]=$line
    let "i++"
    # reading from file path
done < "<yourFullFilePath>"

шаг 2 : теперь повторяем и выводим вывод:

for line in "${array[@]}"
  do
    echo "$line"
  done

echo конкретный индекс в массиве : доступ к переменной в массиве:

echo "${array[0]}"
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.