Как добавить статический контент в одну ячейку существующего файла CSV

Предположим, мне нужно добавить приведенное ниже содержимое в первую ячейку (а не построчно) существующего файла CSV, содержащего сведения о клиенте. Как я могу этого достичь?

Добавляемый контент:

"This is Loganayaki ,she is trying to append the csv file

But she is not able to, she is facing difficulty using shell script

she is seeking help to fix this issue, so that she cab complete her task.
she tried few things which is not helping her"

Customer_File:

ID,Customer_Name,Cust_ADD
1,A,CBE
2,B,POL
3,C,POL

Я попробовал приведенный ниже код

#!/bin/bash

# File paths

csv_file = "data.csv"

# New content to prepend

new_content = "This is Loganayaki ,she is trying to append the csv file

But she is not able to, she is facing difficulty using shell script

she is seeking help to fix this issue, so that she cab complete her task.
she tried few things which is not helping her"

# Read existing content of the CSV file (excluding the first line)

existing_content=$(tail -n +2 "$csv_file")

# Combine new content with existing content

combined_content = "$new_content"$'\n'"$existing_content"

# Write the combined content back to the CSV file

echo "$combined_content" > "$csv_file"

Он добавляется, но new_content добавляется в три разные строки, а \n оказывается пустой строкой.

Мое ожидание

    This is Loganayaki ,she is trying to append the csv file
    
    But she is not able to, she is facing difficulty using shell script
    
    she is seeking help to fix this issue, so that she cab complete her task.
    she tried few things which is not helping her
    
   
   
   ID,Customer_Name,Cust_ADD
    1,A,CBE
    2,B,POL
    3,C,POL

🤔 А знаете ли вы, что...
Bash поддерживает механизмы перенаправления ввода и вывода для команд и скриптов.


116
4

Ответы:

Я думаю, что если он просто добавляется к первым строкам файла csv, вам просто нужно заменить existing_content=$(tail -n +2 "$csv_file") на existing_content=$(cat "$csv_file"), так как вы хотите сохранить все исходное содержимое файла.

#!/bin/bash

# File paths

csv_file = "data.csv"

# New content to prepend

new_content = "This is Loganayaki ,she is trying to append the csv file

But she is not able to, she is facing difficulty using shell script

she is seeking help to fix this issue, so that she cab complete her task.
she tried few things which is not helping her"

# Read existing content of the CSV file (excluding the first line)

existing_content=$(cat "$csv_file")

# Combine new content with existing content

combined_content = "$new_content"$'\n\n\n'"$existing_content"

# Write the combined content back to the CSV file

echo "$combined_content" > "$csv_file"

Это отредактирует файл так, чтобы он был

This is Loganayaki ,she is trying to append the csv file

But she is not able to, she is facing difficulty using shell script

she is seeking help to fix this issue, so that she cab complete her task.
she tried few things which is not helping her


ID,Customer_Name,Cust_ADD
1,A,CBE
2,B,POL
3,C,POL

Результат, который вы описываете в «Мое ожидание...», может быть достигнут с помощью

if tf=$(mktemp)
then
  printf %s "$new_content" >$tf
  cat "$csv_file" >>$tf
  mv -- "$tf" "$csv_file"
fi

Или — адаптируя идею @EdMorton:

if tf=$(mktemp)
then
  {
    printf %s "$new_content"
    cat "$csv_file"
  } >$tf
  mv -- "$tf" "$csv_file"
fi

Конечно, файл CSV больше не будет действительным CSV, как упомянул @tripleee в своем комментарии, но это то, о чем вы просили.


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

Вкратце: чтобы текстовый файл был действительным файлом CSV, он должен удовлетворять нескольким простым ограничениям.

  • Каждая запись должна содержать одинаковое количество полей (обычно строка является записью; но поскольку поле может содержать символы новой строки, это не совсем так).
  • Любое поле, содержащее буквальную кавычку, символ новой строки или разделитель, должно быть заключено в кавычки. (По определению разделителем является запятая ,, но существует множество распространенных вариантов, таких как TSV, где вместо этого разделителем является символ табуляции, а также варианты, разделенные точкой с запятой, вертикальной чертой и т. д.) Другие поля также могут быть заключены в кавычки, но это совершенно необязательно. Чтобы закодировать буквальный символ-разделитель внутри поля в кавычках, удвойте его.

Существуют диалекты с немного другими правилами, но это, безусловно, самые распространенные соглашения.

Итак, чтобы добавить это многострочное значение в поле, вам нужно заключить его в кавычки; и вам необходимо сохранить существующую структуру полей.

(Чтобы упростить следующее изложение, я буду использовать более короткое значение, чем то, которое вам нужно. Мы добавим поле

foo, "bar"
baz!

Вы заметите, что он содержит буквальную запятую, буквальные кавычки и буквальную новую строку.)

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

ID,Customer_Name,Cust_ADD
"1foo, ""bar""
baz!",A,CBE
2,B,POL
3,C,POL

Если вы хотите заменить, а не добавить, очевидно, удалите 1, который был старым значением этой ячейки. Чтобы добавить (или заменить) имя первого поля в строке заголовка, должно быть очевидно, что нужно изменить (т. е. выбрать первую строку вместо второй).

Таким образом, задача состоит в том, чтобы применить необходимые изменения к значению перед манипулированием файлом.

#!/bin/bash

csv_file = "data.csv"

new_content = "This is Loganayaki ,she is trying to append the csv file

But she is not able to, she is facing difficulty using shell script

she is seeking help to fix this issue, so that she cab complete her task.
she tried few things which is not helping her"

# Apply necessary transformations
replacement=${new_content//\"/\"\"}
replacement=${replacement//$'\n'/$'\\\n'}

# Replace
sed -i "2s/\([^,]*\),/\"\1$replacement\",/" "$csv_file"

Простой скрипт sed заменяет первое поле и запятую после него во второй строке на открывающую кавычку, предыдущее значение (\1), новый текст, закрывающую кавычку и запятую, эффективно добавляя новый текст к существующему значению. в первой ячейке этой строки.

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

sed -i "2s/^[^,]*,/$replacement,/" "$csv_file"

И, как отмечалось выше, если вы хотите настроить таргетинг на другую строку, отличную от второй, измените адрес номера строки 2.

Демо: https://ideone.com/5o54dE

Это довольно хрупко, потому что

  • Он не будет работать, если текст замены будет содержать косую черту. Вы можете изменить сценарий sed, чтобы использовать другой разделитель, или выполнить другое преобразование, чтобы добавить обратную косую черту перед каждой буквальной косой чертой. На самом деле, вам нужно сделать это для каждой буквальной обратной косой черты или амперсанда.

    replacement=${replacement//[\\/&]/\\&}
    

    При этом используется расширение параметра Bash , которое не переносимо на sh (тогда как исходный скрипт не использовал синтаксис Bash и, таким образом, вполне мог иметь #!/bin/sh shebang . Возможно, см. также Разница между sh и ругайся)

    Вы заметите, что мы уже используем нечто похожее на обратную косую черту для каждого символа новой строки для sed. Вышеупомянутую замену необходимо будет выполнить перед той, которая добавляет обратную косую черту перед новой строкой.

  • Это может привести к необычным поломкам, если существующее поле уже заключено в кавычки. Для этой цели было бы несложно написать немного другое регулярное выражение. (Например, разрешите закрывающую кавычку непосредственно перед запятой и в этом случае опустите начальную кавычку из нового значения. Регулярное выражение должно допускать двойные двойные кавычки или любые символы, которые не являются двойными кавычками, в первом поле. Если вы хотите чтобы реализовать обе возможности, сценарий должен быть немного сложнее, хотя и ненамного.)

Некоторые из вышеперечисленных уродств проистекают из неудачной границы интерфейса между оболочкой и sed. Вы часто видите скрипты sed, собранные из строковых переменных оболочки, но это приводит ко многим проблемам, когда вам нужно различными способами массировать строки, чтобы сделать их подходящими для sed. Учитывая это, для сценариев Awk доступен гораздо более понятный интерфейс; но стандартный Awk не имеет удобного редактирования sed -i на месте (что тоже не является стандартным, но довольно распространено). Тогда вам просто нужно записать временный файл и переименовать его, чтобы заменить входной файл. Кроме того, синтаксис Awk более подробный (но и менее предназначен только для записи).

t=$(mktemp -t replace.XXXXXXXX) || exit
trap 'rm -f "$t"; exit' ERR EXIT HUP INT
awk -F , -v replace = "$new_content" '
BEGIN { OFS=FS; gsub(/"/, "\"\"", replace) }
FNR==2 { $1 = "\"" $1 replace "\"" } 1' "$csv_file" >"$t" &&
mv "$t" "$csv_file"

Демо: https://ideone.com/DoPH8x

Это потребует более обширного рефакторинга, чтобы правильно обрабатывать цитируемые поля во входных данных.

Однако на этом этапе вместо того, чтобы усложнять сценарий sed или Awk, возможно, стоит обратиться к Python, чей модуль csv сделает за вас все это и многое другое.

import csv
import sys
 
 
csv_file = "data.csv"
 
new_content = """This is Loganayaki ,she is trying to append the csv file
 
But she is not able to, she is facing difficulty using shell script
 
she is seeking help to fix this issue, so that she cab complete her task.
she tried few things which is not helping her"""
 
with open(csv_file) as inp:
    rows = csv.reader(inp)
    writer = csv.writer(sys.stdout)
    for lineno, row in enumerate(rows, 1):
        if lineno == 2:
            row[0] += new_content
        writer.writerow(row)

Демо: https://ideone.com/dSp7DK


Решено

Не пытайтесь прочитать весь входной файл в памяти, просто используйте временный файл:

#!/usr/bin/env bash

tmp=$(mktemp) &&
trap 'rm -f "$tmp"; exit' EXIT &&
{
    printf '%s' "$new_content" &&
    cat -- "$csv_file"
} > "$tmp" &&
mv -- "$tmp" "$csv_file"

Символы && необходимы, чтобы не испортить входной файл, если на предыдущем шаге что-то не удалось. Если вас беспокоит сохранение разрешений и т. д. исходного файла, измените mv -- "$tmp" "$csv_file" на cat -- "$tmp" > "$csv_file".