Если вы хотите обрабатывать что-либо, кроме простейших 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
, сам по себе, у вас есть проблемы, с которыми я не могу помочь вам: -)