6

I have a variable, var, that contains:

XXXX YY ZZZZZ\n
aaa,bbb,ccc

All I want is aaa in the second line. I tried:

out=$(echo "$var" | awk 'NR==2{sub(",.*","")}' )

but I get no output. I tried using , as the FS but I can't get the syntax right. I really want to learn awk/regex syntax.

I want to use out as a variable "$out" somewhere else -- not to print.

3
  • re. "I want to use out as a variable "$out" somewhere else not to print.", the command to extract a particular part of the input is the same regardless of if you capture the output with a command substitution or not. (well, as long as you use an external command to do it anyway.) Just wrap the out=$(...) around it.
    – ilkkachu
    Sep 15 at 10:03
  • 1
    It is impossible for the script you posted to produce the error message you say it does. We can't help you if you don't show us the problem you need help with. By the way, having a variable that contains a multi-line string is usually a sign that you have the wrong approach earlier in your script.
    – Ed Morton
    Sep 15 at 13:14
  • 1
    Please try harder to explain what you want.  For starters, EXACTLY what output do you want for the sample input you show: aaa, XXXX YY ZZZZZ<newline>aaa, XXXX YY ZZZZZ\naaa or XXXX YY ZZZZZ\n<newline>aaa?  Then, show more examples (of input and corresponding desired output).  For example, (1) only one line, (2) more than two lines, (3) first line contains comma(s), (4) second line doesn’t contain commas, etc.  Try to cover all the functionality that you want as thoroughly as you can.  … (Cont’d) Sep 16 at 21:15
17

You don't want regexes there. The entire point of awk is to automatically split a line into fields, so just set the field separator to , and print the first field of the second line:

$ printf '%s' "$var" | awk -F, 'NR==2{print $1}'
aaa

Or, if your shell supports <<<:

$ awk -F, 'NR==2{print $1}' <<<"$var"
aaa

If you really want to do it manually and not use awk as intended, you can do:

$ awk 'NR==2{sub(/,.*/,""); print}' <<<"$var"
aaa

You were getting no output because you didn't tell awk to print anything.

0
8

Alternatively, you could also use the ${param#pattern} and ${param%%pattern} standard parameter expansion operators here:

NL='
'
out=${var#*"$NL"} # removes first line. Assumes there are at least 2
out=${out%%"$NL"*} # removes all but the first line
out=${out%%,*} # removes everything after the first ,

Or with bash specifically, you could use:

LC_ALL=C # needed to accept non-text
[[ $var =~ ^[^$'\n']*$'\n'([^,$'\n']*) ]]
out=${BASH_REMATCH[1]}

Standardly, there's also expr:

NL='
'
out=$(LC_ALL=C expr "x$var" : "[^$NL]*$NL\([^,$NL]*\)")

The problem in your approach is that you don't tell awk to print anything. If awk doesn't print anything, then nothing will be stored in the variable as $(...) expands to the output of the command inside¹. Also, remember echo can't be used to print arbitrary data.

out=$(printf '%s\n' "$var" | awk 'NR == 2 {sub(",.*", ""); print}')

Or:

out=$(printf '%s\n' "$var" | awk -F, 'NR == 2 {print $1}')

¹ minus the trailing newline characters, and with behaviour varying between shell implementations if the output contains NUL bytes

4
  • I don't want to print the results. I want to use the output as a variable. Sep 15 at 9:46
  • @Dark_Stoner, see edit. You do want awk to print something so that output can be stored in the variable. Sep 15 at 9:59
  • I got you. Thanks so much. Starting to get a handle on what aw requires. Sep 15 at 14:38
  • I;m going to use your solutions because they register with my thought process Sep 16 at 0:02
7

Another option using sed:

sed -n 's/,.*$//p' <<< "$var"
  • This will substitute (s/../../) everything starting from the first , on each line to the end of the line (,.*$) with "nothing", thereby leaving only the part before the first ,.
  • By using the -n option, output is suppressed by default. The p at the end of the program instructs sed to still print lines where the "search" pattern was found. That way, we ignore the first line (without ,) and only process the second line, where a , was actually found.

As usual, you can import the result into a shell variable via command substitution:

out=$(sed -n 's/,.*$//p' <<< "$var")

or, in shells that don't understand here-strings,

out=$(printf '%s' "$var" | sed -n 's/,.*$//p')

Notice that since you didn't include examples of fringe cases, it is difficult to taylor the solution to accomodate all possible content of $var. The current solution assumes that there is only one line with a ,, from which you want to extract the first field.

3
  • I can't tell you how much that helped me out. Thanks so much. Sep 15 at 17:52
  • While this will probably work for the OP, if the first line contains a comma, it will produce extra stuff. This should be trivially fixable by adding a 2 to the beginning of the sed expressions.
    – Matthew
    Sep 16 at 17:41
  • @Matthew The problem is that the OP's description doesn't include fringe cases, so it is impossible to tell if the first line could also include a ,, or whether it is actually the last line the OP wants to parse and this only happens to coincide with the second line in the given example. You are right that your modification would accomodate the former case, but then the answer would be a replication of the other answer by Praveen Kumar so I will just mention the limitations in a caveat.
    – AdminBee
    Sep 17 at 7:10
6
awk -F, '/,/ {print $1}' <<< "$var"

This tells awk to use a comma character as the field delimiter, and to search for any line which contains a comma. Once a line containing a comma is found, awk is told to print the first field from that line, i.e. everything up to but not including the first comma.

1
3

Using sed:

$ sed -n '2s/,.*//p' <<<"$var"
aaa

specifically if you are reading from a large file it's good to set a break point after your command proceed the second line.

$ sed -n '2{s/,.*//p;q;}' infile

this will quit the later processing on the input file.

0
2

Another way using awk could be:

awk -F, '{ getline; print $1 }' <<<"$var"

The field delimiter is , then getline will jump to the next line and print the first record

Edge case:

If var contains the following:

var="foo bar baz\n
aaa,bbb,ccc\n
qux foo\n
ddd,eee,fff"

then

aaa
ddd

would be printed.

9
  • 1
    If the input contains only one line, that will print the first field of the first line though. Sep 15 at 8:00
  • in general, it'll print all even-numbered lines, and if the input has an odd number of lines, also the last odd line. Which is not the same as just printing the second line, like the code in the question does.
    – ilkkachu
    Sep 15 at 8:06
  • I agree. I might have taken the question too verbatim. For the above usecase it works but I can imagine that if the var contains foo\nbar\nbaz\nqux it would print bar and qux. For this very specific case imho it does just that. Sep 15 at 8:09
  • @ValentinBajrami I suggest to add notes to your answer that this code will produce the expected result with the input shown in the question and what would happen for other possible input.
    – Bodo
    Sep 15 at 8:56
  • 1
    I can't tell you how much that helped me out. Thanks so much. Hmm, I guess II just did tell you. Sep 15 at 17:55
1
echo "XXXX YY ZZZZZZZ
aaa,bbb,ccc" | awk -F,  '{print $1}'
XXXX YY ZZZZZZZ
aaa
0
1

Using cut:

cut -d, -f1

will give you the first field (-f1) until the delimiter -d

If you want the last line only, you can pipe from tail. Or pipe from sed to get a specific line.

tail -n1 <<< "$var" | cut -d, -f1
sed -n 2p <<< "$var" | cut -d, -f1
1
  • Thanx for your input. haven't used cut yet yet. Sep 17 at 6:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.