Using Raku (formerly known as Perl_6)
These answers use the Raku programming language, specifically in conjunction with its Text::CSV
module. Answer below is in two parts: the first part reads a file linewise, the second part reads the file in all at once. Either method can be used to wrangle text, outputting RFC-4180 (and possibly RFC-4180/RFC-7111) conforming files.
Linewise:
Reading a file linewise, as a 'one-liner' accepting commandline input:
~$ raku -MText::CSV -ne 'my $csv = Text::CSV.new; $csv.parse($_); put $csv.strings.raku;' MS.csv
Sample Input:
https://www.microsoft.com/en-us/download/details.aspx?id=45485
Sample Output:
["User Name", "First Name", "Last Name", "Display Name", "Job Title", "Department", "Office Number", "Office Phone", "Mobile Phone", "Fax", "Address", "City", "State or Province", "ZIP or Postal Code", "Country or Region"]
["chris\@contoso.com", "Chris", "Green", "Chris Green", "IT Manager", "Information Technology", "123451", "123-555-1211", "123-555-6641", "123-555-9821", "1 Microsoft way", "Redmond", "Wa", "98052", "United States"]
["ben\@contoso.com", "Ben", "Andrews", "Ben Andrews", "IT Manager", "Information Technology", "123452", "123-555-1212", "123-555-6642", "123-555-9822", "1 Microsoft way", "Redmond", "Wa", "98052", "United States"]
["david\@contoso.com", "David", "Longmuir", "David Longmuir", "IT Manager", "Information Technology", "123453", "123-555-1213", "123-555-6643", "123-555-9823", "1 Microsoft way", "Redmond", "Wa", "98052", "United States"]
["cynthia\@contoso.com", "Cynthia", "Carey", "Cynthia Carey", "IT Manager", "Information Technology", "123454", "123-555-1214", "123-555-6644", "123-555-9824", "1 Microsoft way", "Redmond", "Wa", "98052", "United States"]
["melissa\@contoso.com", "Melissa", "MacBeth", "Melissa MacBeth", "IT Manager", "Information Technology", "123455", "123-555-1215", "123-555-6645", "123-555-9825", "1 Microsoft way", "Redmond", "Wa", "98052", "United States"]
Above uses Raku's -ne
linewise flags to take input line by line, and output parsed strings. Above, drop the call to .raku
to output parsed text without doublequotes/escapes. Alternatively, the code below takes input line by line using $csv.getline()
, which stores data internally as a Raku array, which is then output:
Linewise: Raku code as a script (useful for data-wrangling):
use Text::CSV;
my @rows;
my $csv = Text::CSV.new;
my $fh = open "MS.csv", :r, :!chomp;
while ($csv.getline($fh)) -> $row {
@rows.push: $row;
}
$fh.close;
$_.raku.put for @rows;
Linewise: Raku code as a 'one-liner' accepting STDIN
:
~$ raku -MText::CSV -e 'my @rows; my $csv = Text::CSV.new; \
while ($csv.getline($*IN)) -> $row { @rows.push: $row; }; \
$_.raku.put for @rows;' < MS.csv
Sample Output (last two code examples above: below shows Raku's internal data representation):
$["User Name", "First Name", "Last Name", "Display Name", "Job Title", "Department", "Office Number", "Office Phone", "Mobile Phone", "Fax", "Address", "City", "State or Province", "ZIP or Postal Code", "Country or Region"]
$["chris\@contoso.com", "Chris", "Green", "Chris Green", "IT Manager", "Information Technology", "123451", "123-555-1211", "123-555-6641", "123-555-9821", "1 Microsoft way", "Redmond", "Wa", "98052", "United States"]
$["ben\@contoso.com", "Ben", "Andrews", "Ben Andrews", "IT Manager", "Information Technology", "123452", "123-555-1212", "123-555-6642", "123-555-9822", "1 Microsoft way", "Redmond", "Wa", "98052", "United States"]
$["david\@contoso.com", "David", "Longmuir", "David Longmuir", "IT Manager", "Information Technology", "123453", "123-555-1213", "123-555-6643", "123-555-9823", "1 Microsoft way", "Redmond", "Wa", "98052", "United States"]
$["cynthia\@contoso.com", "Cynthia", "Carey", "Cynthia Carey", "IT Manager", "Information Technology", "123454", "123-555-1214", "123-555-6644", "123-555-9824", "1 Microsoft way", "Redmond", "Wa", "98052", "United States"]
$["melissa\@contoso.com", "Melissa", "MacBeth", "Melissa MacBeth", "IT Manager", "Information Technology", "123455", "123-555-1215", "123-555-6645", "123-555-9825", "1 Microsoft way", "Redmond", "Wa", "98052", "United States"]
If all you want is the first two lines (e.g. header plus first data row), change:
while ($csv.getline($*IN))
...to... for ($csv.getline($*IN) xx 2)
Printing just a single row (e.g. the header) will return a \n
newline separated list (useful for checking correct parsing):
"User Name"
"First Name"
"Last Name"
"Display Name"
"Job Title"
"Department"
"Office Number"
"Office Phone"
"Mobile Phone"
"Fax"
"Address"
"City"
"State or Province"
"ZIP or Postal Code"
"Country or Region"
Of course, individual columns can be output as well. Simply change the final put
statement to:
.[2].raku.put for @rows;
...to print out the 3rd Column ("Last Name", zero-index = 2):
"Last Name"
"Green"
"Andrews"
"Longmuir"
"Carey"
"MacBeth"
Reading a file all at once: Raku's Text::CSV
module provides a high-level csv(…)
function to alter/validate CSV/TSV text, and output RFC-4180 (and possibly RFC-4180/RFC-7111) conforming files.
Convert a CSV file to a TSV file (reads stdin
, outputs columns with internal whitespace as double-quoted by default):
$ cat MS.csv | raku -MText::CSV -e 'my @a = csv(in => $*IN); csv(in => @a, sep => "\t", out => $*OUT);' > MS.tsv
Sample Output (saved and re-opened in Vim w/ CSV module):
"User Name" "First Name" "Last Name" "Display Name" "Job Title" Department "Office Number" "Office Phone" "Mobile Phone" Fax Address City "State or Province" "ZIP or Postal Code" "Country or Region"
[email protected] Chris Green "Chris Green" "IT Manager" "Information Technology" 123451 123-555-1211 123-555-6641 123-555-9821 "1 Microsoft way" Redmond Wa 98052 "United States"
[email protected] Ben Andrews "Ben Andrews" "IT Manager" "Information Technology" 123452 123-555-1212 123-555-6642 123-555-9822 "1 Microsoft way" Redmond Wa 98052 "United States"
[email protected] David Longmuir "David Longmuir" "IT Manager" "Information Technology" 123453 123-555-1213 123-555-6643 123-555-9823 "1 Microsoft way" Redmond Wa 98052 "United States"
[email protected] Cynthia Carey "Cynthia Carey" "IT Manager" "Information Technology" 123454 123-555-1214 123-555-6644 123-555-9824 "1 Microsoft way" Redmond Wa 98052 "United States"
[email protected] Melissa MacBeth "Melissa MacBeth" "IT Manager" "Information Technology" 123455 123-555-1215 123-555-6645 123-555-9825 "1 Microsoft way" Redmond Wa 98052 "United States"
Raku's Text::CSV
module can handle alternative column separators (e.g. \t
), double-quoted column values with embedded commas, newlines, etc. Most importantly, when wrangling text input you'll have access to Raku's powerful Regex pattern-matching interface, should you need to filter/substitute characters (e.g. column-names), rows, or columns. Each Raku code sample above correctly parses the OP's short sample text and outputs the following (or similar; declare Text::CSV.new(:allow_whitespace)
to trim whitespace before/after each field):
["this, is the first entry", "this is the second", "34.5"]
See the URLs below for storing data in a hash (or array-of-hashes), and/or for setting column-names
, sep-char
, escape-char
, formula-handling
, binary
, strict
parameters, etc.
https://raku.land/github:Tux/Text::CSV
https://github.com/Tux/CSV
https://raku.org