Bash: Построчное Чтение Файла — While Read Line Цикл. Запись и чтение из файла

Цикл while — это лучший способ построчного чтения файла в Linux.

В этой статье я покажу общий синтаксис конструкции while read line в Bash и пример того, как построчно прочитать файл из командной строки в Linux.

Также я приведу пример Bash скрипта, который построчно читает передаваемый ему файл и выводит каждую строку, добавляя к ней определенный текст.

Дельный Совет: Сделайте Bash скрипт интерактивным! Научите его спрашивать «Да/Нет» для подтверждения.

Цикл While Read Line в Bash

Общая структура конструкции while read line , которая может использоваться в Bash скриптах:

While read LINE do COMMAND done

Та же конструкция одной строкой (легко использовать из командной строки в Linux):

While read LINE; do COMMAND; done

В качестве примера выведем список всех пользователей из файла /etc/passwd:

$ while read LINE; do echo "$LINE" | cut -f1 -d":"; done LINE в этой конструкции — это название переменной, в которой сохраняется значение текущей строки во время каждой итерации цикла.

В зависимости от содержимого вашего файла (FILE), для удобства, вы можете назвать эту переменную как вам будет угодно.

Например, если вы храните в файле список пользователей, будет лучше если вместо LINE вы назовете эту переменную USER , и тогда это будет выглядеть таким образом:

$ cat users.txt Eric Kyle Stan Kenny $ while read USER; do echo "Привет $USER!"; done

Дельный Совет: Выполняйте одну команду N раз подряд с помощью цикла FOR в Bash!

Bash Скрипт: Построчное Чтение Файла

Создадим Bash скрипт, который в качестве аргумента получает путь к файлу и печатает слова "Это строка:" в начали каждой строки этого файла.

Создайте пустой файл readfile.sh с помощью команды touch readfile.sh .

Сделайте его исполняемым с помощью chmod +x readfile.sh .

Откройте файл readfile.sh в текстовом редакторе и скопируем туда следующий код:

#!/bin/bash FILE=$1 while read LINE; do echo "Это строка: $LINE" done

Дельный Совет: Не будьте занудой! Сделайте ваш Bash скрипты Ц В Е Т Н Ы М ! Пусть он выглядит ВЕЛИКОЛЕПНО!

Сохраните изменения в запустите скрипт:

$ ./script.sh /etc/passwd Это строка: root:x:0:0:root:/root:/bin/bash Это строка: bin:x:1:1:bin:/bin:/sbin/nologin Это строка: daemon:x:2:2:daemon:/sbin:/sbin/nologin [...]

Аналогично , PHP реализует несколько подходов к чтению информации, хранящейся в них. Существует ряд функций, позволяющих получать как всё содержимое файла целиком, так и отдельные строки. Эти функции используются для решения различных задач.

Если необходимо прочитать всё содержимое файла для дальнейшей обработки, используйте функцию file_get_contents() . Достаточно передать ей путь до файла в перовом параметре. Функция вернет результат в виде строки, которую можно поместить в переменную.

//Читаем всё содержимое файла в строку $content = file_get_contents("some-file-name.txt"); echo $content;

Также существует возможность получить всё содержимое файла в виде массива, где каждый элемент соответствует одной строке. За данный функционал в PHP отвечает функция file() . Как и в случае с file_get_contents() , ей достаточно передать всего один параметр - путь до файла.

//Получаем содержимое файла в виде массива $content = file("some-file-name.txt"); //Перебираем все элементы массива в цикле foreach ($content as $string) { echo $string; }

Заметка
Нужно быть аккуратным при чтении файлов целиком. Если они содержат много данных, их сохранение в переменных или массивах может вызвать большой расход оперативной памяти. Всё зависит от поставленной задачи и программной логики. Возможно, построчное чтение будет лучшим вариантом.

Построчное и посимвольное чтение

Обработка больших файлов требует оптимизации потребления оперативной памяти. PHP имеет функции, способные осуществлять построчное и символьное чтение. Они работают с файловыми дескрипторами, что требует написания немного большего количества программного кода, нежели при использовании file_get_contents() или file() . Однако это не является существенной проблемой.

Для построчного чтения нужно использовать функцию fgets() . В первом параметре она принимает дескриптор файла. По умолчанию читается всё содержимое строки до символа её перевода. В случае достижения конца файла, функция вернет логическое значение false . Таким образом, построчное чтение можно организовать в виде цикла.

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

//Поочередно получаем строки и выводим в браузер $descriptor = fopen("some-file-name.txt", "r"); if ($descriptor) { while (($string = fgets($descriptor)) !== false) { echo $string; } fclose($descriptor); } else { echo "Невозможно открыть указанный файл"; }

Некоторые задачи, решаемые в PHP, требуют последовательного получения каждого символа в файле. Для этого нужно использовать функцию fgetc() . Её применение схоже с предыдущей, за исключением того, что fgetc() принимает всего один параметр - дескриптор файла.

//Выводим каждый символ из файла с новой строки $descriptor = fopen("some-file-name.txt", "r"); if ($descriptor) { while (($char = fgetc($descriptor)) !== false) { echo $char. "\n"; } fclose($descriptor); } else { echo "Невозможно открыть указанный файл"; }

Работа с бинарными файлами и потоковыми соединениями делает функцию fgets() практически бесполезной, так как в них могут отсутствовать переводы строк. В PHP есть специальная функция fread() , позволяющая читать заданное количество байт из файлового дескриптора. Она имеет два параметра: дескриптор файла и количество читаемых байтов. При этом оба параметра являются обязательными, а переводы строк игнорируются.

//Открываем файл и читаем его целиком, функция //filesize() возвращает размер файла в байтах $descriptor = fopen("some-file-name.txt", "r"); if ($descriptor) { echo fread($descriptor, filesize("some-file-name.txt")); fclose($descriptor); } else { echo "Невозможно открыть указанный файл"; }

Две важнейших операции, касающиеся содержимого файлов - ввод (чтение) из файла и вывод (запись) в файл.

Для работы с содержимым файла программа должна его открыть . Открывая файл, мы указываем, что именно мы хотим с ним делать: читать или писать. Результатом открытия является так называемый файловый дескриптор - скалярное значение, хранящее сведения о текущем состоянии процесса чтения или записи. Операции чтения, записи и прочие операции ввода вывода в дальнейшем осуществляются через дескриптор.

Открывается файл при помощи встроенной процедуры open . Процедура получает три параметра: переменную для создаваемого дескриптора, строку, обозначающую режим открытия (чтение, запись или что-то ещё), и, наконец, имя файла:

Perl

my $file ; open $file , "<" , "MyFile.txt" ; открытие для чтения

Мы обожаем совмещать объявление переменной для дескриптора с вызовом процедуры open:

Perl

open my $file , "<" , "MyFile.txt" ;

Значение "<" , переданное как второй параметр, символизирует чтение. Знак «меньше», повернувшийся спиной к имени файла, как бы намекает нам, что готовится извлечение информации из файла. Угадайте, как открыть файл для записи? Конечно же так:

Perl

open my $file , ">" , "MyFile.txt" ; открытие для записи

В примерах использования процедуры open указывалось относительное имя файла - по отношению к директории, являющейся для данной программы текущей. Сразу после запуска программы это та директория, из которой запущена программа. Но текущая директория может быть изменена процедурой chdir:

Perl

chdir "/" ;

Нет никаких противопоказаний к тому, чтобы задавать при открытии абсолютное имя файла:

Perl

open my $passwd , "<" , "/etc/passwd" ;

В некоторых операционных системах, в полных именах файлов применяется другой разделитель директорий. Например, в Microsoft DOS и Microsoft Windows вместо слэша / применяется разделитель бэкслэш \ . Тем не менее, задавая полное имя в программе на Perl при открытии, следует использовать слэш: "/c:/dos/autoexec.bat" .

Интуиция подсказывает нам, что всё, что открывается, должно быть рано или поздно закрыто. Это верно и для файлов. Любой сценарий ввода/вывода устроен одинаково: открытие, собственно ввод или вывод, и, наконец, закрытие

Закрытие файла освобождает ресурсы операционной системы, занятые при открытии, и гарантирует, что данные, записанные в файл, дойдут по назначению. После закрытия переменная-дескриптор теряет актуальность и не может быть использована для ввода/вывода.

Для закрытия файлов служит встроенная процедура close:

Perl

close $file ;

Не стоит ожидать, что открытие файла всегда будет успешным. Есть много причин, которые могут помешать. Среди них:

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

В этих и подобных случаях все дальнейшие операции с файлом потеряют всякий смысл и лишь приведут Perl в ярость. Однако процедура open в своём возвращаемом значении сообщает об итоге операции: при успехе возвращает истинное значение, а при неудаче - ложное. Хорошо написанная программа должна проверять это значение, и действовать в зависимости от него. Например, при неудачном открытии завершить работу программы с выдачей соответствующего сообщения:

Perl

if (open ) { работа с файлом и последующее закрытие } else { die ; }

Обратите внимание на специальную переменную $! . В случае возникновения ошибки в эту переменную автоматически помещается текст, объясняющий причину ошибки, например, Нет такого файла или каталога или Отказано в доступе или что-то ещё. Текст в $! , в зависимости от системных настроек, может быть на другом языке.

Гораздо изящней выглядит следующая идиома:

Perl

open or die "Невозможно открыть файл: $!\n" ;

В этом логическом выражении два операнда. Если первый (то, что возвращает open) принимает истинное значение, то в вычислении второго нет нужды, так как всё выражение уже заведомо истинно. Если же open возвратит ложное значение, то значение всего выражения определяется по второму операнду, который в этом случае должен быть вычислен. Для вычисления будет вызвана процедура die со всеми вытекающими последствиями.

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

Perl

for (@fileNames ) { open my $file , "<" , $_ or warn "Невозможно открыть файл $_: $!" and next ; работа с очередным файлом и последующее его закрытие }

Имеется два способа чтения из дескриптора: побайтное/посимвольное и построчное.

При побайтном/посимвольном чтении файла программа запрашивает у открытого дескриптора очередную порцию данных нужного размера и предоставляет скалярную переменную для запрошенных данных. Для чтения служит встроенная процедура read:

Perl

read $file , $buffer , 16 ;

Можно представлять себе файл, открытый для чтения, как последовательность байтов. Воображаемый указатель отделяет уже прочитанную часть последовательности от ещё непрочитанной. Операция чтения приводит, помимо прочего, к сдвигу указателя к концу файла. За счёт этого следующая команда чтения получит доступ к новой порции данных. Дескриптор файла хранит в себе разнообразную информацию об открытом файле, и, в том числе, этот указатель - номер первого непрочитанного байта. Сразу после открытия указатель равен нулю.

Что же будет, если запросить при чтении больше байт, чем размер непрочитанной части файла? Ничего страшного, компьютер не сломается. Просто в переменную-буфер отправится меньше байт, чем было запрошено. Контролировать это явление удобно, пользуясь возвращаемым значением процедуры read - это количество байт, которое удалось прочесть. Широко распространена при программировании на Perl такая идиома:

Perl

while (read $file , $buffer , $size ) { сделать что-то с $buffer }

Здесь значение, возвращённое из read , используется в качестве условия цикла. Рано или поздно файл будет прочитан до конца, и следующий вызов read возвратит ноль (ложное значение). Это прервёт цикл, что нам, собственно, и нужно.

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

Построчное чтение осуществляется оператором <…> . Код <$file > приводит к считыванию очередной строки из дескриптора $file в переменную по умолчанию $_ .

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

Perl

open my $file , "<" , "file.txt" or die "Невозможно открыть файл: $!\n" ; while (<$file >) { print ; }

Здесь читатель справедливо задаётся вопросом: что именно печатает процедура print в теле цикла? Переменную по умолчанию, конечно. Можно было бы написать print $_ , но вряд ли это добавит ясности.

Другой пример. В уже открытом файле записаны числа, по одному в строке. Требуется вывести на экран их сумму.

Perl

my $sum =0 ; while (<$file >) { chomp ; $sum +=$_ ; } print "$sum\n" ;

Команда chomp необходима вот по какой причине. Оператор <…> вместе со строкой считывает и завершающий строку символ, который создаст проблему, если считанная строка впоследствии будет участвовать в арифметическом выражении. Встроенная процедура chomp удаляет этот символ, если строка заканчивается им. Если же последний символ другой, процедура ничего не делает. такая предосторожность нужна на тот случай, если, к несчастью, файл не заканчивается символом конца строки. Тогда и последняя прочитанная из файла строка закончится чем-то другим. Имеется также процедура chop , которая удаляет и возвращает последний символ строки независимо от того, какой он. Обе процедуры, chop и chomp , работают со строкой, переданной как параметр, но в отсутствие параметра - с переменной $_ .

Не следует думать, что с оператором построчного чтения мы обречены на использование переменной $_ . Если требуется читать в другую переменную, используем присваивание:

Perl

$s =<$file >;

Самое время сообщить об одной особенности оператора построчного чтения. Его смысл немного меняется, если оператор поместить в списочный контекст, то есть туда, где должен быть список. Например, если присвоить массиву:

Perl

@s =<$file >;

В этом случае все строки, прочитанный из файла, заполнят массив. Ещё можно организовать переборный цикл:

Perl

print for <$file >;

Этот код, как и приведённый выше цикл while , печатает строки файла на экран. К тому же результату приведёт код

Perl

print <$file >;

(здесь пропущено слово for ). Но мы не рекомендуем такой подход, поскольку здесь сначала прочитываются все строки из файла, а затем передаются как список параметров в процедуру print . При этом все строки без всякой необходимости занимают память, а при большом файле объём памяти может быть очень велик.

Вообще, построчное чтение может создать похожую проблему, если в файле имеются очень длинные строки. И хотя это не характерно для текстовых файлов (например, созданных в текстовом редакторе), следует принимать это обстоятельство во внимание.

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

Для записи в дескриптор применяется давно знакомая нам процедура print , но не совсем так, как мы привыкли:

Perl

print $file $string ;

Здесь содержимое строки $string записывается в открытый дескриптор $file . Обратите особое внимание на отсутствие запятой после первого параметра $file . С запятой смысл команды будет другим: в дескриптор ничего не запишется, а вместо этого программа выведет на экран строковое представление значений обеих переменных $file и $string:

GLOB(0x989e830)Привет!

(шестнадцатеричное число в скобках, скорее всего, будет другим, ну и вместо слова Привет! может оказаться другой текст). Число это нам ни о чём не говорит, как и загадочное слово GLOB вместе со скобками. Итак, если все параметры процедуры print разделены запятыми, все они печатаются на экран. Если после первого параметра нет запятой, этот параметр должен быть дескриптором, открытым для записи, а остальные параметры записываются в него:

Perl

print $file "Hello, " , $user ;

Можно все параметры после дескриптора заключить в скобки:

Perl

print $file ("Hello, " , $user );

Один из видных специалистов по языку Perl рекомендует заключать дескриптор в фигурные скобки, чтобы зрительно отделить его от остальных параметров:

Perl

print {$file } "Hello, " , $user ;

Программисты часто встречаются с ситуацией, когда требуется в шаблонный текст в определённых местах поместить изменяющиеся фрагменты. Perl хорошо приспособлен для решения таких задач. Изменяющиеся части текста очень удобно поместить в переменную, а имя переменной вставить в нужное место в строку, заключённую в двойные кавычки:

Perl

print "Уважаемый $name! Вы задолжали $duty рублей. Уплатите до $date, иначе $punishment.\n" ;

Таким способом можно вставлять в шаблон и строки, и числа. Что касается чисел, в каких-то ситуациях может потребоваться их особое форматирование. Числа перед включением в шаблон может потребоваться округлить до нужного количества десятичных цифр после точки, дополнить слева нужным количеством нулей или пробелов, чтобы число хорошо смотрелось в таблице. Может потребоваться вставить число в двоичном или шестнадцатеричном формате.

В таких ситуациях на помощь приходят встроенные процедуры printf и sprintf . Первый параметр в обеих процедурах - так называемая форматная строка . Это шаблонный текст, в который с определённые места должны быть вставлены остальные параметры. Места вставки параметров и желательный формат специальным образом помечаются. Метка представляет собой знак процента, за которым следует обозначение формата. Остальные параметры процедуры вставляются на места форматных меток в соответствующем формате. Процедура printf выводит результат в дескриптор подобно процедуре print , а sprintf возвращает полученную после всех вставок строку для какого-то иного использования. В принципе, если бы не было printf , её можно было бы легко запрограммировать: вызов printf $_ for 1. .10 ;

0.70 0.05 0.41 0.75 0.29 0.67 0.95 0.54 0.75 0.26

Приведённые примеры не охватывают все возможные форматы, понятные процедурам printf и sprintf , но для наших задач будет достаточно и этого.

Использование знака процента как признака метки лишает нас возможности использовать его в форматной строке как таковой. Эта трудность не новая для нас, и разрешается она уже хорошо знакомым способом. Комбинация из двух знаков процента означает одиночный знак процента (подобно тому, как \\ внутри "" -строк означает один бэкслэш):

Perl

$p =38 ; $x =43 ; printf "%d%% от %d равно %d\n" , $p , $x , $p /100 *$x ;

38% от 43 равно 16

Точно так же, как и print , процедура printf может осуществлять вывод не только на экран, но и дескриптор файла, открытого для записи или для добавления:

Perl

printf {$file } ;

Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: