Я новичок в написании сценариев Bash и все еще на стадии обучения. В настоящее время я работаю над файлами CSV, и существует требование: если две строки совпадают в двух разных файлах CSV, следует добавить столбец данных.
Например,
csv_file1.csv:
Account,Entity,Data
A1101,E101,1000
csv_file2.csv:
Account,Entity,Data
A1101,E101,2000
A1101,E102,2000
При выполнении сценария bash ожидаемым результатом должен быть новый файл CSV со строками:
Account,Entity,Data
A1101,E101,3000
A1101,E102,2000
Поскольку элементы в 1-й строке «csv_file1» и элементы в 1-й строке «csv_file2» совпадают, данные в столбце «Данные» должны быть добавлены.
Ниже приведен код, который я попытался распечатать содержимое обоих файлов CSV. Но я не могу двигаться вперед отсюда.
#!/bin/bash
#assigns the CSV file to a variable
FILE1 = "csv_file1.csv"
#reads each line and splits it into three variables
while IFS=, read -r Account Entity Data; do
echo "$Account#""$Entity#""$Data"
done < <(tail -n +2 $FILE1)
#assigns the CSV file to a variable
FILE2 = "csv_file2.csv"
#reads each line and splits it into three variables
while IFS=, read -r Account Entity Data; do
echo "$Account#""$Entity#""$Data"
done < <(tail -n +2 $FILE2)
Было бы здорово, если бы вы, ребята, помогли мне со своими идеями. пожалуйста, дайте мне знать, если потребуется какая-либо дополнительная информация. Заранее большое спасибо!
С помощью PHP я создаю массив с учетной записью и сущностью в качестве индекса. Если ключ массива не существует, создайте его, иначе добавьте значение.
== файл: chk.awk ==
@load "filefuncs"
{
#// input: file=xxx.csv
#// check if $file defined
if (file= = "") {
printf ("Please use `-v file=xxx.csv` to define a csv file\n");
exit 2;
}
#// check if inout file exist
ret=stat(file,fdata)
if (ret<0) {
printf ("File '%s' seems not exist !?\n", file);
exit 3;
}
printf ("* NR=%d\n", NR);
printf ("1st file:'%s'\n", $0);
#// Read from 2nd file
ret = getline sec < file
if (ret)
printf ("2nd file:'%s'\n", sec);
else
printf ("2nd file already EOF\n");
#//? Terminated ?
} END {
#// 1st file END, but how about 2nd?
if (ret) {
printf ("2nd left: --\n");
while (ret) {
ret = getline sec < file
if (ret) printf ("2nd: '%s'\n", sec );
}
}
}
командная строка:
$ gawk -k -v file=2.csv -f chk.awk 1.csv
* NR=1
1st file:'Account,Entity,Data'
2nd file:'Account,Entity,Data'
* NR=2
1st file:'A1101,E101,1000'
2nd file:'A1101,E101,2000'
2nd left: --
2nd: 'A1101,E102,2000'
2nd: 'A1101,E102,2000'
2nd: 'A1101,E102,2000'
2nd: 'A1101,E102,2000'
или
$ gawk -k -v file=1.csv -f chk.awk 2.csv
* NR=1
1st file:'Account,Entity,Data'
2nd file:'Account,Entity,Data'
* NR=2
1st file:'A1101,E101,2000'
2nd file:'A1101,E101,1000'
* NR=3
1st file:'A1101,E102,2000'
2nd file already EOF
* NR=4
1st file:'A1101,E102,2000'
2nd file already EOF
* NR=5
1st file:'A1101,E102,2000'
2nd file already EOF
* NR=6
1st file:'A1101,E102,2000'
2nd file already EOF
Теперь вы можете сами сравнить разницу между этими двумя файлами.
В вашем скрипте bash
отсутствует сравнение двух входных данных и сумма при совпадении строк.
Если вы абсолютно хотите сделать это всего лишь с помощью bash
, если ваши файлы CSV такие же простые, как то, что вы показываете (без полей в кавычках, без новых строк в полях...), если вы хотите также печатать строки, которые встречаются только в одном из два входных файла, и если вас не волнует окончательный порядок, вы можете попробовать:
#!/bin/bash
declare -iA a=()
declare -i Data
declare Account Entity
FILE1 = "csv_file1.csv"
FILE2 = "csv_file2.csv"
head -n1 "$FILE1"
while IFS=, read -r Account Entity Data; do
a["$Account,$Entity"]+=Data
done < <(tail -n +2 "$FILE1"; tail -n +2 "$FILE2")
for k in "${!a[@]}"; do
printf '%s,%d\n' "$k" ${a[$k]}
done
По соображениям производительности awk
будет лучшим выбором, чем bash
. С теми же предположениями, что и для версии bash
, вы можете попробовать следующее с любым awk
:
awk '
BEGIN { FS = OFS = "," }
NR == 1 { print; next }
FNR == 1 { next }
{ a[$1 OFS $2] += $3 }
END { for(k in a) print k, a[k] }
' csv_file1.csv csv_file2.csv
Мы объявляем запятую разделителем входных (FS
) и выходных (OFS
) полей. Мы печатаем строку заголовка первого файла и пропускаем строку заголовка второго файла.
При анализе файлов мы сохраняем содержимое в массиве a
с двумя первыми полями в качестве ключа и последним полем в качестве значения (например, a[A1101,E101] = 1000
), добавляя третье поле к значению, если ключ уже существует.
В конце (END
) мы печатаем содержимое массива a
.
Если ваш формат CSV более сложный, с полями в двойных кавычках, возможно, содержащим экранированные двойные кавычки (""
) или даже символы новой строки, вы можете попробовать опцию --csv
в последних версиях GNU awk
:
awk --csv '
function quote(s) {
if (s ~ /[",\n]/) {
gsub(/"/, "\"\"", s)
s = "\"" s "\""
}
return s
}
BEGIN { OFS = "," }
NR == 1 { print; next }
FNR == 1 { next }
{ a[$1][$2] += $3 }
END { for(k1 in a) for(k2 in a[k1]) print quote(k1), quote(k2), a[k1][k2] }
' csv_file1.csv csv_file2.csv
Демо:
$ cat csv_file1.csv
Account,Entity,Data
A1101,E101,1000
A1101,"E101,E102",3000
A1101,"hello, ""world""",1000
$ cat csv_file2.csv
Account,Entity,Data
A1101,E101,2000
A1101,E102,2000
A1101,"E101,E102",2000
A1101,"hello, ""world""",4000
$ awk --csv -f foo2.awk csv_file*
Account,Entity,Data
A1101,"hello, ""world""",5000
A1101,E101,3000
A1101,E102,2000
A1101,"E101,E102",5000
Но тогда настоящий синтаксический анализатор CSV, вероятно, будет безопаснее.
Привет, Рено, сработало чудесно. Спасибо за помощь! Из любопытства, можно ли упорядочить данные, заключенные в двойные кавычки? как и в случае со столбцом Entity, если есть данные типа «E101, E102», код awk игнорирует эти двойные кавычки.
Это было бы возможно, но тогда вам придется анализировать более сложный формат CSV. Что делать, если в некоторых полях вы избежали двойных кавычек? Я не рекомендую анализировать сложный CSV без специального анализатора. Вы можете попробовать вариант --csv
GNU awk
, но даже он имеет ограничения. Или вы можете попробовать один из многочисленных наборов инструментов CSV (например, csvkit).
Если ваш CSV более сложный, чем тот, который вы показываете в своем вопросе, я отредактировал свой ответ, чтобы показать, как использовать опцию --csv
в последних версиях GNU awk
.
Почему бы не использовать
awk
? ` -k --csv Включить специальную обработку для файлов со значениями, разделенными запятыми (CSV). См. раздел «Работа с файлами значений, разделенных запятыми». Эту опцию нельзя использовать с --posix. Попытка сделать это приводит к фатальной ошибке. `-F fs --field-separator fs