1

I'm creating a bash script to write variable values to a csv file. But unable to write particular values that contain " and ' symbols in the line. For example, I have variables and values like this available into the shell:

username: my-name@abc.com

name: my-name

password: j50z54"#7b'y'/3l7H%7

If try to write them to a csv file like this:

  echo -e "username,password,name" >> abc.csv
  echo -e "$username,$password,$name" >> abc.csv

I get an error:

line 2: unexpected EOF while looking for matching `"'
line 3: syntax error: unexpected end of file

Thre problem occurs due to the presence of double quotes and single quotes in the value.

Method 1:

Store the variable value as follows:

  • escape all double quotes and single quotes by prepending them with a backslash \
  • enclose the entire variable value within double quotes.

For example, an actual value of password j50z54"#7b'y'/3l7H%7 should be saved as "j50z54\"#7b\'y\'/3l7H%7"

Then if I run the following script:

  echo -e "username,password,name" >> abc.csv
  echo -e "$username,$password,$name" >> abc.csv
  sed -i 's/\\//g' abc.csv

I get the proper csv file:

username,password,name
my-name@abc.com,j50z54"#7b'y'/3l7H%7,my-name

BUT, the problem is that it is not possible to modify the variable value ( at its source ) as in the above-mentioned format.

Method 2:

I tried a metho as shown here, the script:

username=my-name@abc.com
name=my-name
cat >> abc.csv << \EOF
Username,Password,Name
$username,j70z38"#7k'y'/3l7H%9,$name
EOF

gives output file abc.csv:

Username,Password,Name
$username,j70z38"#7k'y'/3l7H%9,$name

(please ignore the fact that password value is directly provided here for just testing purposes)

In this method, variable names like $username and $name are printed instead of their values.

Question:

Is there any other better way/script to write the variable value properly to a CSV file?

ADDITIONAL INFORMATION:

This script is used for automation inside a bash task of the Azure DevOps pipeline. Also, the password is saved in a variable group there and is available inside the pipeline as $(password).

AnjK
  • 2,887
  • 7
  • 37
  • 64
  • 3
    Are you sure that's the cause of the error? I can do this on the prompt just fine: `password="j50z54\"#7b'y'/3l7H%7"; echo -e "$password"`. – Thomas May 31 '23 at 18:17
  • Usually CSV does not use `\"` to escape quotes. In most dialects, CSV uses double quotes around the fields, and then uses double double quotes (`""`) to represent a single double quote in the field. – Thomas May 31 '23 at 18:19
  • 1
    The `echo` commands you listed *cannot* cause an "unexpected EOF" error. The problem is somewhere else, maybe where you set the variable(s), maybe because the script is running through some unusual parsing process (e.g. being run through some automation process, etc). Be sure to give a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) showing the problem, so we can see everything involved in causing it. – Gordon Davisson May 31 '23 at 18:41
  • 2
    Side note: `echo -e` is almost always a mistake; it is non-standard, non-portable, and prone to unexpected behavior. Do you actually want it to interpret backslash-escape sequences in the strings? If not, use e.g. `printf '%s\n' "$username,$password,$name"` or `printf '%s,%s,%s\n' "$username" "$password" "$name"`. If you do want escape sequences parsed, use `%b` instead of `%s` in the format string. – Gordon Davisson May 31 '23 at 18:45
  • I can't imagine a variation on your method 2 that produces the output you claim it does, in which some variable references are expanded and others not. But if you want the variable references in the heredoc to be expanded, then don't quote (any part of) the delimiter. – John Bollinger May 31 '23 at 18:51
  • I don't understand the problem you attribute to method 1. What would constitute modifying a variable's value "at its source"? – John Bollinger May 31 '23 at 18:54
  • 2
    I think we need a *bona fide* [mre] that demonstrates your issue, and describes both actual and expected results. I suspect, however, that you're conflating two or more layers here, such as how the data are represented in several distinct contexts. – John Bollinger May 31 '23 at 18:57
  • `n this method, variable names are printed, not their values.` Or `username` is equal to `$username` and `sa_name` is equal to `$sa_name` (literal). Please post the output of `declare -p username password sa_name` before executing your script. – KamilCuk May 31 '23 at 18:58
  • `echo` doesn't work in general. It's best to use it (if at all) only for fixed simple strings. Otherwise use `printf`. See the accepted, and excellent, answer to [Why is printf better than echo?](https://unix.stackexchange.com/q/65803/264812). – pjh May 31 '23 at 22:10
  • @GordonDavisson You are right, this script is used inside a pipeline. Updated that as additional information in the question. – AnjK Jun 01 '23 at 04:25
  • @JohnBollinger Thanks for pointing that out. I updated the question there with the correct details. – AnjK Jun 01 '23 at 04:26
  • @KamilCuk Thanks for pointing that out. Some corrections are made under section of method 2. – AnjK Jun 01 '23 at 04:31
  • `<< \EOF` Remove the \ . It's still very much unclear to me - just `echo "$username,$password,$name" >> abc.csv`. For some Bash has confusing quoting and expansion rules, you could consider using a different language. Also, for csv you have to properly handle `,` separator. So consider `python import csv` or other language with builtin csv support. – KamilCuk Jun 01 '23 at 07:45

1 Answers1

2

Piecing together the clues, I think your situation is something along these lines:

  • the script receives the username, password, and name from some external source (arguments, interactive input, file ...) and stores them in variables.

  • you want to write a file containing CSV representations of those values

  • your first attempt, with echo, writes the values as received, but your (separate) CSV reader cannot read them back successfully afterward

  • your "Method 1" describes a proof-of-concept variation in which the username, password, and name are set explicitly in the script, instead of being read from an external source as they ultimately need to be.

  • your "Method 2" is presumably another proof-of-concept variation

However, I do not accept this:

unable to write particular values that contain " and ' symbols in the line.

The example presented to support that claim in fact emits such values flawlessly, and, separately, the error message you present cannot have come from those lines. My best guess is that the error comes from a CSV reader, in which case it reflects not that you failed to write the data, but that the resulting file was not valid CSV (as far as that reader was concerned). Do note, however, that if the data contained any literal backslashes (\) then echo -e would screw that up. If you want to use echo (which you could) then drop the -e.

Additionally, the file resulting from your method 1 would be rejected by some CSV readers on account of containing a double quote in an unquoted field, and it would be rejected by most CSV readers if the double quote appeared at the beginning of the field instead of in the middle. It may be what you expected, but it is not well characterized as a "proper" csv file by most standards.

The issue you seem to be struggling with is that CSV requires special handling for some characters. You cannot just dump raw character data into a CSV file and expect readers to understand it. You need to format the data appropriately for CSV. This has nothing specifically to do with Bash.

Note also that there is a pretty big diversity of formats that fall under the "CSV" umbrella. Not all of them even use commas as field delimiters. If you are targeting a specific CSV dialect then you will need to format your output as that dialect requires. That will surely include some form of quoting for fields that include the field delimiter, and very likely some form of quoting for fields that include the quoting character(s). If your dialect allows fields that contain the record delimiter, then it will also require those fields to be quoted.

If you're not sure what your CSV dialect requires, or if you're flexible about that, then know that CSV dialects predominantly use the double-quote character (") for quoting, and generally they support quoting only on a whole-field basis. And typically, the designated means for escaping a double quote inside a quoted field is to double it. Some CSV dialects expect all fields to be quoted, and that's compatible with most dialects, so unless you know that you need to do differently, that's what I would recommend.

Example:

#!/bin/bash

# Input the data
read -r -p 'username: ' username
read -r -p 'name: '     name
read -r -p 'password: ' password

# Double all quotation marks (") in the field values
username_csv=${username//\"/\"\"}
name_csv=${name//\"/\"\"}
password_csv=${password//\"/\"\"}

# Write the output file with quotes and field delimiters
cat >output.csv <<EOF
Username,Password,Name
"$username_csv","$password_csv","$name_csv"
EOF

Demo

$ bash example.sh
username: my-name@abc.com
name: my-name
password: j50z54"#7b'y'/3l7H%7
$
$ cat output.csv
Username,Password,Name
"my-name@abc.com","j50z54""#7b'y'/3l7H%7","my-name"
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • This worked. Thank you so much. One of the major mistakes from my side was in Method 2 line: `cat >> abc.csv << \EOF`. The back-slash just before `EOF` was not needed there. I removed it and then the script worked with `cat >> abc.csv << EOF`. – AnjK Jun 01 '23 at 09:09