105

Let's say I have the following string:

something1:    +12.0   (some unnecessary trailing data (this must go))
something2:    +15.5   (some more unnecessary trailing data)
something4:    +9.0   (some other unnecessary data)
something1:    +13.5  (blah blah blah)

How do I turn that into simply

+12.0,+15.5,+9.0,+13.5

in bash?

codeforester
  • 28,846
  • 11
  • 78
  • 104
Alex Coplan
  • 12,581
  • 17
  • 72
  • 135
  • 1
    Let's step back for a moment and consider this thread a glaring indictment of bash as a programming language. Consider Scala's `listOfStuff mkString ", "`, or Haskell's `intercalate ", " listOfString` – F. P. Freely Aug 03 '18 at 15:49
  • 1
    Related: [Convert text file into a comma delimited string](https://stackoverflow.com/q/53093449/6862601) – codeforester Nov 02 '18 at 22:32

18 Answers18

183

Clean and simple:

awk '{print $2}' file.txt | paste -s -d, -
kvantour
  • 20,742
  • 4
  • 38
  • 51
Mattias Ahnberg
  • 2,341
  • 1
  • 11
  • 9
101

You can use awk and sed:

awk -vORS=, '{ print $2 }' file.txt | sed 's/,$/\n/'

Or if you want to use a pipe:

echo "data" | awk -vORS=, '{ print $2 }' | sed 's/,$/\n/'

To break it down:

  • awk is great at handling data broken down into fields
  • -vORS=, sets the "output record separator" to ,, which is what you wanted
  • { print $2 } tells awk to print the second field for every record (line)
  • file.txt is your filename
  • sed just gets rid of the trailing , and turns it into a newline (if you want no newline, you can do s/,$//)
Dan Fego
  • 12,448
  • 5
  • 43
  • 57
22
cat data.txt | xargs | sed -e 's/ /, /g'
  • I like solutions like this too but is the -e arg necessary here since there's only the first command being used for sed? I believe `cat data.txt | xargs | sed 's/ /, /g'` would work all the same. For example, `echo -e "foo\nbar\nbazz" | xargs | sed 's/ /, /g'` outputs **foo, bar, bazz**. – John Pancoast Feb 04 '21 at 01:05
11
$ awk -v ORS=, '{print $2}' data.txt | sed 's/,$//'
+12.0,+15.5,+9.0,+13.5

$ cat data.txt | tr -s ' ' | cut -d ' ' -f 2 | tr '\n' ',' | sed 's/,$//'
+12.0,+15.5,+9.0,+13.5
kev
  • 137,128
  • 36
  • 241
  • 259
  • cheers, what about if the input to awk was through standard input (just put `function | awk...` in your example? – Alex Coplan Jan 03 '12 at 15:21
11

This might work for you:

cut -d' ' -f5 file | paste -d',' -s
+12.0,+15.5,+9.0,+13.5

or

sed '/^.*\(+[^ ]*\).*/{s//\1/;H};${x;s/\n/,/g;s/.//p};d' file
+12.0,+15.5,+9.0,+13.5

or

sed 's/\S\+\s\+//;s/\s.*//;H;$!d;x;s/.//;s/\n/,/g' file

For each line in the file; chop off the first field and spaces following, chop off the remainder of the line following the second field and append to the hold space. Delete all lines except the last where we swap to the hold space and after deleting the introduced newline at the start, convert all newlines to ,'s.

N.B. Could be written:

sed 's/\S\+\s\+//;s/\s.*//;1h;1!H;$!d;x;s/\n/,/g' file
potong
  • 47,186
  • 6
  • 43
  • 72
10

awk one liner

$ awk '{printf (NR>1?",":"") $2}' file

+12.0,+15.5,+9.0,+13.5
Rahul Verma
  • 2,776
  • 11
  • 26
  • Format specifier `"%s",` should be added after `printf` to make it more robust i.e. to make it work with all kind of rows such as "foo %s". – jarno Nov 30 '20 at 10:35
8

This should work too

awk '{print $2}' file | sed ':a;{N;s/\n/,/};ba'
jaypal singh
  • 67,706
  • 21
  • 93
  • 138
5

Try this easy code:

awk '{printf("%s,",$2)}' File1
Vonton
  • 2,008
  • 3
  • 16
  • 25
4

You can use grep:

grep -o "+\S\+" in.txt | tr '\n' ','

which finds the string starting with +, followed by any string \S\+, then convert new line characters into commas. This should be pretty quick for large files.

kenorb
  • 118,428
  • 63
  • 588
  • 624
3

try this:

sedSelectNumbers='s".* \(+[0-9]*[.][0-9]*\) .*"\1,"'
sedClearLastComma='s"\(.*\),$"\1"'
cat file.txt |sed "$sedSelectNumbers" |tr -d "\n" |sed "$sedClearLastComma"

the good thing is the easy part of deleting newline "\n" characters!

EDIT: another great way to join lines into a single line with sed is this: |sed ':a;N;$!ba;s/\n/ /g' got from here.

Aquarius Power
  • 3,212
  • 5
  • 26
  • 56
2

A solution written in pure Bash:

#!/bin/bash

sometext="something1:    +12.0   (some unnecessary trailing data (this must go))
something2:    +15.5   (some more unnecessary trailing data)
something4:    +9.0   (some other unnecessary data)
something1:    +13.5  (blah blah blah)"

a=()
while read -r a1 a2 a3; do
    # we can add some code here to check valid values or modify them
    a+=("${a2}")
done <<< "${sometext}"
# between parenthesis to modify IFS for the current statement only
(IFS=',' ; printf '%s: %s\n' "Result" "${a[*]}")

Result: +12.0,+15.5,+9.0,+13.5

  • Alternatively you could use `read -r -a cols` and thereafter add `"${cols[1]}` to the list `a`. – jarno Nov 30 '20 at 22:11
2

Don't seen this simple solution with awk

awk 'b{b=b","}{b=b$2}END{print b}' infile
ctac_
  • 2,295
  • 2
  • 5
  • 16
0

Another Perl solution, similar to Dan Fego's awk:

perl -ane 'print "$F[1],"' file.txt | sed 's/,$/\n/'

-a tells perl to split the input line into the @F array, which is indexed starting at 0.

Chris Koknat
  • 2,636
  • 2
  • 25
  • 27
0

Well the hardest part probably is selecting the second "column" since I wouldn't know of an easy way to treat multiple spaces as one. For the rest it's easy. Use bash substitutions.

# cat bla.txt
something1:    +12.0   (some unnecessary trailing data (this must go))
something2:    +15.5   (some more unnecessary trailing data)
something4:    +9.0   (some other unnecessary data)
something1:    +13.5  (blah blah blah)

# cat bla.sh
OLDIFS=$IFS
IFS=$'\n'
for i in $(cat bla.txt); do
  i=$(echo "$i" | awk '{print $2}')
  u="${u:+$u, }$i"
done
IFS=$OLDIFS
echo "$u"

# bash ./bla.sh
+12.0, +15.5, +9.0, +13.5
Marki
  • 590
  • 7
  • 21
0

Yet another AWK solution

Run

awk '{printf "%s", $c; while(getline){printf "%s%s", sep, $c}}' c=2 sep=','

to use the 2nd column to form the list separated by commas. Give the input as usual in standard input or as a file name argument.

jarno
  • 621
  • 9
  • 21
0

With perl:

fg@erwin ~ $ perl -ne 'push @l, (split(/\s+/))[1]; END { print join(",", @l) . "\n" }' <<EOF
something1:    +12.0   (some unnecessary trailing data (this must go))
something2:    +15.5   (some more unnecessary trailing data)
something4:    +9.0   (some other unnecessary data)
something1:    +13.5  (blah blah blah)
EOF

+12.0,+15.5,+9.0,+13.5
fge
  • 110,072
  • 26
  • 223
  • 312
0

You can also do it with two sed calls:

$ cat file.txt 
something1:    +12.0   (some unnecessary trailing data (this must go))
something2:    +15.5   (some more unnecessary trailing data)
something4:    +9.0   (some other unnecessary data)
something1:    +13.5  (blah blah blah)
$ sed 's/^[^:]*: *\([+0-9.]\+\) .*/\1/' file.txt | sed -e :a -e '$!N; s/\n/,/; ta'
+12.0,+15.5,+9.0,+13.5

First sed call removes uninteresting data, and the second join all lines.

Elias Dorneles
  • 19,202
  • 9
  • 63
  • 95
0

You can also print like this:

Just awk: using printf

bash-3.2$ cat sample.log
something1:    +12.0   (some unnecessary trailing data (this must go))
something2:    +15.5   (some more unnecessary trailing data)
something4:    +9.0   (some other unnecessary data)
something1:    +13.5  (blah blah blah)

bash-3.2$ awk ' { if($2 != "") { if(NR==1) { printf $2 } else { printf "," $2 } } }' sample.log
+12.0,+15.5,+9.0,+13.5