Bash скрипт для добавления двойных кавычек в файл с разделителями-запятыми .CSV - PullRequest
1 голос
/ 15 апреля 2020

Мне нужно добавить двойные кавычки в CSV-файл. Мой пример данных выглядит следующим образом ..

378478,COMPLETED,Tracfone,,,"2020/03/29 09:39:22",,2787,,356074101197544,89148000005748235454,75176540
378328,COMPLETED,"Total Wireless","Unlimited Talk, Text, & Data (First 25GB High Speed, then unlimited 2GB)",50,"2020/03/29 06:10:01",200890899011202395,0899,0279395,356058102052972,89148000005117597971,67756296

Я пробовал некоторый код, доступный онлайн с awk и sed, это приводит к тому, как показано ниже, Ошибка - ** Первая ди git в номер обрезается, как например. в '378478' он отображает только '78478'.

Также он добавляет двойные кавычки к уже существующим двойным кавычкам! ** Ничто, кажется, не работает идеально. Пожалуйста, ведите меня!

"78478","COMPLETED","Tracfone","","",""2020/03/29 09:39:22"","","2787","","356074101197544","89148000005748235454","75176540"
"78328","COMPLETED",""Total Wireless"",""Unlimited Talk"," Text"," & Data (First 25GB High Speed"," then unlimited 2GB)"","50",""2020/03/29 06:10:01"","200890899011202395","0899","0279395","356058102052972","89148000005117597971","67756296"
"78329","COMPLETED",""Cricket Wireless"",""Unlimited Talk"," Text"," & 4G LTE Data w/ 15GB Hotspot"","60",""2020/03/29""

Это код, который я использую:

awk -F"'?,'?" -v OFS='","' '{$1=$1; gsub(/^.|$/,"\"")} 1' file # or
sed -E 's/([^,]*) , (.*)/"\1" , "\2"/' file

Ниже приведен мой общий код. Я намеревался сначала преобразовать все .xlsx в .csv, а затем добавить двойные кавычки в тот же файл csv и сохранить его в том же файле. Я знаю, что часть $ file.csv неверна, поэтому мне нужна помощь

find "$Src_Dir" -type f -iname "*.xlsx" -print>path/temp

cat path/temp | while IFS="" read -r -d $'\0' file; 
do
    echo $file
    ssconvert "${file}" --export-type=Gnumeric_stf:stf_csv
    awk -F"'?,'?" -v OFS='","' '{$1=$1; gsub(/^.|$/,"\"")} 1' $file > $file.csv
done

Ответы [ 2 ]

4 голосов
/ 15 апреля 2020

Если вы хотите обрабатывать что-либо, кроме простейших CSV-файлов, вам, вероятно, следует переместить на из sed и awk. Доступны гораздо лучшие инструменты.

Например, если вы sudo apt install csvtool (или эквивалентный) в вашем любимом дистрибутиве, вы можете использовать его функциональность для каждой строки во входном файле. См. Следующий сценарий для примера:

#!/bin/bash

function quotify {
  # Start empty line, process every field.

  line=""
  while [[ $# -ne 0 ]] ; do
      #    Append comma for all but first field, then quoted field.

      [[ -n "${line}" ]] && line="${line},"
      line="${line}\"$1\""

      shift
  done

  # Output the fully quoted line.

  echo "${line}"
}

# Needed to call functions. Also, ensure link: /bin/sh -> /bin/bash.
export -f quotify

# Pretty-print input and output.

echo "Input file:"
sed 's/^/   /' inputFile.csv

echo "Output file:"
csvtool call quotify inputFile.csv | sed 's/^/   /'

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

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

Input file:
   378478,COMPLETED,Tracfone,,,"2020/03/29 09:39:22",,2787,,356074101197544,89148000005748235454,75176540
   378328,COMPLETED,"Total Wireless","Unlimited Talk, Text, & Data (First 25GB High Speed, then unlimited 2GB)",50,"2020/03/29"
Output file:
   "378478","COMPLETED","Tracfone","","","2020/03/29 09:39:22","","2787","","356074101197544","89148000005748235454","75176540"
   "378328","COMPLETED","Total Wireless","Unlimited Talk, Text, & Data (First 25GB High Speed, then unlimited 2GB)","50","2020/03/29"

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

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

  • Пустое пространство имеет значение. Все, что находится между запятыми, считается частью поля. Это особенно важно при обнаружении поля в кавычках: оно должно иметь кавычку в качестве первого символа, а не abc, "d,e,f",ghi, поскольку "d,e,f" не будет обрабатываться правильно.
  • Кавычки разрешено содержать запятые, а "" последовательности внутри них превращаются в ".
  • Возможно, не стоит предоставлять плохо отформатированные файлы CSV: -)

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

Во-первых, функция для поиска позиции, если какая-то строка в другой строке, полезна для определения границ поля:

function findPos {
    haystack="$1"
    needle="$2"

    # Remove everything past the needle.

    prefix="${haystack%%${needle}*}"

    # If nothing was removed, it wasn't found, so supply massive number.
    # Otherwise, it was found at the length of the string with removed stuff.

    position=999999
    [[ ${#prefix} -ne ${#haystack} ]] && position=${#prefix}
    echo ${position}
}

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

function getNextFieldLen {
    line="$1"

    # Empty line means all work done.

    [[ -z "${line}" ]] && echo -1 && return

    # Handle unquoted first, this is easy.

    [[ "${line:0:1}" != '"' ]] && { echo $(findPos "${line}" ","); return; }

    # Now handle quoted. Loop over all segments where a segment is defined as
    # the text up to the next <"">, assuming it's before the next <",>.

    field=""
    nextQuoteComma=$(findPos "${line}" '",')
    nextDoubleQuote=$(findPos "${line}" '""')
    while [[ ${nextDoubleQuote} -lt ${nextQuoteComma} ]]; do
        # Append segment to the field and go back for next segment.

        field="${field}${line:0:${nextDoubleQuote}}\"\""
        line="${line:${nextDoubleQuote}}"
        line="${line:2}"

        nextQuoteComma=$(findPos "${line}" '",')
        nextDoubleQuote=$(findPos "${line}" '""')
    done

    # Add final segment (up to the comma) and output entire field.

    field="${field}${line:0:${nextQuoteComma}}\""
    echo "${#field}"
}

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

function quotifyStdIn {
    # Process file line by line.

    while read -r line; do
        # Start with empty output line and non-comma separator.

        outLine="" ; sep=""

        # Place terminator to make processing easier, start field loop.

        line="${line},"
        fieldLen=$(getNextFieldLen "${line}")
        while [[ ${fieldLen} -ge 0 ]]; do
            # Get field and quotify if needed, adjust line (remove field and comma).

            field="${line:0:${fieldLen}}"
            [[ "${field:0:1}" = '"' ]] || field="\"${field}\""

            line="${line:$((fieldLen+1))}"
            #line="${line:${fieldLen}}"
            #line="${line:1}"

            # Append to output line and prepare for next field.

            outLine="${outLine}${sep}${field}"; sep=","

            fieldLen=$(getNextFieldLen "${line}")
        done

        # Output built line.

        echo "${outLine}"
    done
}

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

function quotifyFile {
    file="$1"

    # Empty file or "-" means standard input, otherwise take input from real file.

    [[ ${#file} -eq 0 ]] && { quotifyStdIn; return; }
    [[ "${file}" = "-" ]] && { quotifyStdIn; return; }

    quotifyStdIn < "${file}"
}

И, наконец, потому что каждая программа, которая не является "Hello, world" один из них заслуживает какой-либо формы тестового набора, вот что вы можете использовать для тестирования различных возможностей:

(
    echo 'paxdiablo,was here'
    echo 'and,"then, strangely,",he,was,not'
    echo '50,"My name is ""Pax"", and yours is ""Bob""",42'
    echo '17,"""Love"" is grand",19'
) > harness.csv

echo "Before:"
sed "s/^/   /" harness.csv
echo "After:"
quotifyFile harness.csv | sed "s/^/   /"

rm -rf harness.csv

И, поскольку тестовый набор мало полезен, если вы не запустите тесты Вот результаты первого запуска:

Before:
   paxdiablo,was here
   and,"then, strangely,",he,was,not
   50,"My name is ""Pax"", and yours is ""Bob""",42
   17,"""Love"" is grand",19
After:
   "paxdiablo","was here"
   "and","then, strangely,","he","was","not"
   "50","My name is ""Pax"", and yours is ""Bob""","42"
   "17","""Love"" is grand","19"

Надеюсь, этого будет достаточно, чтобы вы могли работать в отсутствие возможности установки пакетов. Конечно, если один из пакетов, которые вы не можете установить в bash, сам по себе, у вас есть проблемы, с которыми я не могу помочь вам: -)

0 голосов
/ 16 апреля 2020

Ваш начальный CSV не является хорошим CSV: 2 строки имеют разное количество столбцов

+--------+-----------+----------------+--------------------------------------------------------------------------+----+---------------------+---+------+---+-----------------+----------------------+----------+
| 1      | 2         | 3              | 4                                                                        | 5  | 6                   | 7 | 8    | 9 | 10              | 11                   | 12       |
+--------+-----------+----------------+--------------------------------------------------------------------------+----+---------------------+---+------+---+-----------------+----------------------+----------+
| 378478 | COMPLETED | Tracfone       | -                                                                        | -  | 2020/03/29 09:39:22 | - | 2787 | - | 356074101197544 | 89148000005748235454 | 75176540 |
| 378328 | COMPLETED | Total Wireless | Unlimited Talk, Text, & Data (First 25GB High Speed, then unlimited 2GB) | 50 | 2020/03/29          | - | -    | - | -               | -                    | -        |
+--------+-----------+----------------+--------------------------------------------------------------------------+----+---------------------+---+------+---+-----------------+----------------------+----------+

Используя Миллера (https://github.com/johnkerl/miller), вы можете запустить

mlr --csv --quote-all -N unsparsify input >output

иметь

"378478","COMPLETED","Tracfone","","","2020/03/29 09:39:22","","2787","","356074101197544","89148000005748235454","75176540"
"378328","COMPLETED","Total Wireless","Unlimited Talk, Text, & Data (First 25GB High Speed, then unlimited 2GB)","50","2020/03/29","","","","","",""

Вы можете использовать его, загрузив исполняемый файл https://github.com/johnkerl/miller/releases/tag/v5.7.0

...