Главная > Powershell > Чтение части файла через Powershell

Чтение части файла через Powershell

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

Действовал он приблизительно так: “Я читаю файл через Get-Content, потом хочу выделить начальный разделитель, конечный, и прочесть выделенный кусок через SubString. Но ничего не получается”.

В принципе алгоритм верен. Неверно только само чтение файла. Дело в том, что Get-Content читает файл построчно и на выходе получается массив строк:

$file = Get-Content file.txt
$file.GetType()

В данном случае весь файл нужно читать как одну строку. Это можно сделать при помощи метода ReadAllText .NET класса File, и далее как было сказано выше читать выделенный текст в этой строке:

# Читаем файл как одну строку
$file = [System.IO.File]::ReadAllText("file.txt")

# Индекс начала
$start = $file.IndexOf("начало")
# Индекс конца
$end = $file.IndexOf("конец")

# Читаем выделенный текст
$file.Substring($start, $end-$start)
Реклама
Рубрики:Powershell Метки: ,
  1. Ярослав
    19/07/2012 в 09:42

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

  2. Андрей Маркин
    18/04/2015 в 02:36

    Снова здравствуйте, Сергей!

    И теперь уже я повторю фразу из «Пятого элемента»:
    — Хочешь сделать хорошо? — сделай сам!

    Так что и врачи программируют, если больше некому 😉

    Теперь к вопросу:

    А как быть, если метка (Индекс) конца или начала должна быть задана маской, причем она не является единственной?
    Для лучшего понимания покажу:

    Кусок файла:
    ______________________________
    [Матка]
    Дл. матки : 8.10 cm
    Выс. матки : 4.19 cm
    Шир. матки : 5.71 cm
    Объем матки : 101.35 ml
    Толщ. Эндом. : 1.06 cm
    Дл. шейки : 3.79 cm
    Выс. шейки : 3.12 cm
    Шир. шейки : 4.22 cm
    Объем шейки : 26.16 ml

    [Киста]
    Дл. пр. кисты : 2.29 cm
    Выс. пр. кисты : 3.09 cm
    Шир. пр. кисты : 2.83 cm
    Об. пр. кисты : 10.51 ml
    Дл. лев.кисты : 4.82 cm
    Выс. лев.кисты : 2.06 cm
    Шир. лев.кисты : 5.73 cm
    Об. лев.кисты : 29.81 ml

    [Правый яичник]
    Дл. пр. яичника : 5.15 cm
    Выс. пр. яичника : 2.14 cm
    Шир. пр. яичника : 2.13 cm
    Об. пр. яичника : 12.27 ml
    ___________________________

    Задача – разбить файл на секции для дальнейшей обработки.

    $GynReport = Get-Content ‘C:\Test\Gyn_test.txt’-encoding UTF8 -Raw
    $UterusIndexStart = $GynReport.IndexOf(«[Матка]»)
    $UterusIndexEnd = $GynReport.IndexOf(«[Киста]»)
    $UterusSection = $GynReport.Substring($UterusIndexStart, $UterusIndexEnd-$UterusIndexStart)

    Все работает прекрасно… ровно до тех пор пока в тексте есть «[Киста]»
    Но слава Богу такое есть не у каждой пациентки, и когда придет здоровая – такой секции не будет, а следовательно меткой конца секции должна служить либо пустая строка, либо символ «[» либо маска «[*]».
    Появляется проблема – любая такая маска в файле встречаться в том числе ранее секции «[Матка]» и мы получаем:

    Исключение при вызове «Substring» с «2» аргументами: «Длина не может быть меньше нуля. Имя параметра: length»

    Как объяснить PS что индекс конца нужно искать только ниже индекса старта, а не с начала файла? — наиболее логичное решение на мой взгляд.

    Заранее спасибо!

    • 20/04/2015 в 13:07

      На всякий случай по поводу врачей, это пошло отсюда: http://wp.me/p1tmBU-bV, кстати, решение Вам подошло?

      По поводу этого вопроса… как я понимаю, вариант когда меткой конца секции должна служить пустая строка не подходит, потому что если даже метку «Киста» мы слава Богу не найдём, всё равно могут быть другие данные. Т.е. нам нужно искать не конкретно метку «Киста», а символ открывающей квадратной скобки «[«, и следовательно куски текста нужно выбирать между двумя открывающими скобками.

      Можно воспользоваться тем, что метод IndexOf может искать символ в строке, не с самого начала строки, а начиная с указанного символа. Для этого ему нужно указать какой символ искать, и через запятую — начиная с какого символа в строке его нужно искать.

      Т.е. мы можем искать даже не от метки «[Матка]», а от первой открывающей скобки, до второй такой же скобки:

      Стартовый индекс — первая квадратная скобка

      $UterusIndexStart = $GynReport.IndexOf("[")

      В данном случае равносильно строке $UterusIndexStart = $GynReport.IndexOf(«[Матка]»)

      Конечный индекс — вторая открывающая скобка (первая открывающая квадратная скобка, если искать начиная со второго символа в строке)

      $UterusIndexEnd = $GynReport.IndexOf("[",1)

      Дальше как обычно

      $UterusSection =
      $GynReport.Substring($UterusIndexStart, $UterusIndexEnd-$UterusIndexStart)

      (Здесь не влезло, но тут всё без изменений)
      Это мы получили первый блок текста (в нашем случае всю информацию по матке). После чего выбрасываем эту информацию из файла (обрезаем его)

      $GynReport = $GynReport.Substring($UterusIndexEnd)

      и всё в цикле повторяем.
      Если хотите — напишите мне на email — скину код целиком, там будет понятнее.

  3. Олег
    08/12/2015 в 15:21

    Добрый день, Сергей!

    У меня немножко другая задача — есть большой текстовый структурированный файл (табличка) с заголовком в первой строке.
    Нужно разбить его на произвольное количество файлов, но в каждом первой строкой вставить заголовок.
    Я написал кривенький вариант, но мне не нравиться скорость его работы. Как можно оптимизировать решение этой задачи?

    Вот мой вариант:

    # берём переданный параметр и выделяем из него имя и расширение файла
    $split_name = ($args[0] -split «\.»)

    # читаем содержимое файла в переменную
    $fi=Get-Content -Path $args[0] -Encoding oem

    # читаем первую строку и сохраняем её в переменную
    $header = $fi.GetValue(0)

    #устанавливаем счетчик строк
    $ind=1

    #устанавливаем счетчик файлов
    $f_ind=0

    # получаем из параметра размер итоговых файлов (количество строк)
    $split_num = $args[1]

    # пишем строку заголовка в первый файл
    $header | out-file -FilePath ($split_name.GetValue(0) +»_»+($f_ind+1)+».»+$split_name.GetValue(1)) -Encoding oem -Append

    # Цикл пока количество прочитанных строк меньше количества строк в файле
    while ((($f_ind * $split_num)+$ind) -lt $fi.Count )
    {
    # пишем очередную строку в итоговый файл
    $fi.GetValue(($f_ind * $split_num)+$ind) | out-file -FilePath ($split_name.GetValue(0) +»_»+($f_ind+1)+».»+$split_name.GetValue(1)) -Encoding oem -Append

    if ($ind -eq $split_num)
    {
    $ind=0
    $f_ind=$f_ind+1
    $header | out-file -FilePath ($split_name.GetValue(0) +»_»+($f_ind+1)+».»+$split_name.GetValue(1)) -Encoding oem -Append
    }

    # инкрементируем счетчик строк
    $ind=$ind+1
    }

    после обработки первых 300 тысяч строк падает активность Ps. Ощущение что он просто зависает

    • Олег
      09/12/2015 в 06:47

      удалось оптимизировать самому
      новый вариант отрабатывает за 10 секунд. Старый (тот что выше) работал полтора часа
      вот новый вариант:

      $fc=1
      foreach ($mas in (get-content -ReadCount $args[1] -Path $args[0] -Encoding oem ))
      {
      Add-Content -Value $mas -Path $($args[0] -replace «\.»,»_$fc.») -Encoding oem
      ++$fc
      }

      Осталось прикрутить к нему формирование заголовка.

    • Олег
      09/12/2015 в 10:30

      Вот окончательный вариант скрипта

      «Старт $(get-date)»
      $header=get-content -TotalCount 1 -Path $args[0] -Encoding oem
      $fc=1
      foreach ($mas in (get-content -ReadCount $args[1] -Path $args[0] -Encoding oem ))
      {
      Add-Content -Value $mas -Path $($args[0] -replace «\.»,»_$fc.») -Encoding oem
      » $fc $(get-date -DisplayHint Time)»
      ++$fc
      Add-Content -Value $header -Path $($args[0] -replace «\.»,»_$fc.») -Encoding oem
      }
      Remove-Item -Path $($args[0] -replace «\.»,»_$fc.»)
      «Стоп $(get-date)»

      • 09/12/2015 в 12:19

        Скрипт будет работать ещё немного быстрее, если Вы заранее сформируете путь и подставите готовое значение в Add-Content:

        foreach (…)
        {
        $Path = $args[0] -replace «\.», «_$fc.»

        Add-Content -Value $mas -Path $Path
        Add-Content -Value $header -Path $Path

        }

        Потому что как только Powershell видит конструкцию $($Path -replace ‘\.’,’_$fc.’) он каждый раз вычисляет значение выражения (раскрывает скобки, производит операцию замены, и т.д.)

  4. Дима
    28/10/2016 в 10:17

    Здравствуйте. Помогите, пожалуйста. Хочу взять переменные из файла, разделенный на секции. Почему-то в переменные попадает название секции. Как от этого можно избавиться?
    Файл:
    [comps]
    server1
    server2
    [adminusername]
    Скрипт:
    $parameters = [System.IO.File]::ReadAllText(«C:\scripts\config.ini»)
    $ComputersIndexStart = $parameters.IndexOf(«[comps]»)
    $ComputersIndexEnd = $parameters.IndexOf(«[adminusername]»)
    $ComputersSection = $parameters.Substring($ComputersIndexStart, $ComputersIndexEnd-$ComputersIndexStart)
    $ComputersSection
    А вывод переменной $ComputersSection такой:
    [comps]
    server1
    server2
    Почему так?

    • Олег
      28/10/2016 в 18:57

      IndexOf возвращает номер позиции первого символа искомой подстроки.
      Т.е. вашу переменную $ComputersIndexStart нужно увеличить еще на длину строки «[comps]»

  1. 19/07/2012 в 13:29
  2. 09/04/2014 в 14:27

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: