IO. Примеры

Чтение всего содержимого в виде одной строки

readchomp - читает все содержимое (файла, IO, Cmd) в одну строку и удаляет последний перенос строки. Она полезна, когда объем читаемого текста небольшой, особенно, когда известно, что весь текст будет одной строчкой:

`hostname` |> readchomp

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

eachline - на имени файла возвращает итератор строк с символами переноса строки, chomp - удаляет символ переноса, join - объединяет элементы в одну строку через разделитель, println - печатает строку добавляя перенос в конце:

for l in "a.txt"|>eachline
    fields = split(l|>chomp, '*')
    println("fields: ", join(fields, ","))
end

Построчное чтение с обработкой

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

Пусть файл a.txt содержит следующий текст:

животное*кот*5
животное*собака*10
корм*сухарики*15
корм*вискас*20
Генератор

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

( chomp(l) for l in "a.txt"|>eachline )
( chomp(l) for l in "a.txt"|>eachline )|>collect

4-element Array{String,1}:
 "животное*кот*5"
 "животное*собака*10"
 "корм*сухарики*15"
 "корм*вискас*20"
Отступление

Чтобы увидеть результат, в данном примере мы использовали функцию collect, потому что файл, на самом деле - маленький. Нужно помнить, что она материализует все элементы итератора. Когда данных слишком много, чтобы поместиться на экране, REPL выведет на экран лишь часть содержимого. Если вы хотите посмотреть результат чтения большого файла, но он не влезает в память, можно воспользоваться функцией take:

( chomp(l) for l in "a.txt"|>eachline ) |> _->take(_,10) |> collect
Фильтрация

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

( chomp(l) for l in "a.txt"|>eachline if ismatch(r"животное",l) )|>collect

2-element Array{String,1}:
 "животное*кот*5"
 "животное*собака*10"
Поля

Если мы хотим использовать значения отдельных полей:

( split(chomp(l),"*") for l in "a.txt"|>eachline if ismatch(r"животное",l) )|>collect

2-element Array{Array{SubString{String},1},1}:
 SubString{String}["животное","кот","5"]
 SubString{String}["животное","собака","10"]

,после фильтрации строки разбиваются на поля.

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

liner(l)=split(chomp(l),"*") |> fields->(fields[1], fields[2], parse(Int, fields[3]))

, а затем отобрать из оставшихся строк, те, где третье поле, например, больше пяти:

filter( liner(l) for l in "a.txt"|>eachline if ismatch(r"животное",l) ) do fields fields[3]>5 end |> collect

1-element Array{Tuple{SubString{String},SubString{String},Int64},1}:
 ("животное","собака",10)
Именованные поля

Если есть желание работать именованными полями:

names = Dict("категория"=>1, "название"=>2, "вес"=>3)

Dict{String,Int64} with 3 entries:
  "название"  => 2
  "вес"       => 3
  "категория" => 1
filter( liner(l) for l in "a.txt"|>eachline if ismatch(r"животное",l) ) do fields
  fields[names["вес"]]>5 
end |> collect

1-element Array{Tuple{SubString{String},SubString{String},Int64},1}:
 ("животное","собака",10)

и так далее.

Цикл for

Если сложность кода достигает определеного уровня, возможно, будет удобнее воспользоваться классическим циклом for:

names = Dict("категория"=>1, "название"=>2, "вес"=>3)

for l in "a.txt"|>eachline
  fields = split(chomp(l),"*")
  weight = fields[names["вес"]]
  # если в строке не число - то 0:
  weight = ismatch(r"^\d+$", weight) ? parse(Int, weight) : 0
  weight <= 5 && continue 

  # etc ...
  join( STDOUT, fields, "|")
end

Массив строк из файла

eachline работает с объектами типа Cmd (а также с IO):

`zcat a.gz`|>eachline|> _->map(chomp,_) # получили массив строк.

map, помимо основной своей задачи, материализует все элементы итератора.

readlines позволяет прочитать все строки файла ( или Cmd, IO) разом в массив. Здесь чтение всего файла происходит в момент readlines а не по требованию map:

"a.txt"|> readlines |> _->map(chomp,_)

Словарь из файла

Функция filedict будет принимать имя файла (file), разделитель полей (ifs) и, необязательный параметр (reader) - имя программы, которой можно прочитать файл. Если reader не указан, то он принимается равным результату вычисления функции detectreader.

Функция detectreader по имени файла определяет ридер: "zcat" - для файлов *.gz и *.gzip, и "cat" - для всех остальных.

function filedict(file::AbstractString, ifs; reader::String=detectreader(file))
     read_cmd = `$reader $file`
     ll = map( l->split(chomp(l),ifs;limit=2) , read_cmd|>readlines)
     Dict( length(ff)==2 ? ff[1]=>ff[2] : ff[1]=>"" for ff in ll )
end

detectreader(fname::AbstractString) = ismatch(r"\.gz(ip)?$", fname) ? "zcat" : "cat"

Создадим файл, сжатый через gzip файл names.gz с разделителем - пробел.

Напомню, что команды shell можно исполнять в Julia REPL, если перевести REPL в режим командной оболочки, поставив точку с запятой ; в начале строки:

shell> echo -e "cat Vasya\ndog Ahtung" | gzip > names.gz

Проверим на нем нашу функцию:

julia> filedict("./names.gz", r"\s+")
Dict{SubString{String},SubString{String}} with 2 entries:
  "cat" => "Vasya"
  "dog" => "Ahtung"

Другой формат файла (несжатый текстовый файл с разделителем - звездочка):

shell> echo -e "cat*small\ndog*big"  > sizes.txt

julia> filedict("./sizes.txt", "*")
Dict{SubString{String},SubString{String}} with 2 entries:
  "cat" => "small"
  "dog" => "big"

results matching ""

    No results matching ""