16

Given the following data:

 field1;field2;field3;field4;

How to get the number of fields in this string? For example, the command should give back 4 for this case.

Actually, I don't have the same number of contents in each line, but I need to return them all anyway. Once I manage to count the number of fields, I will also manage to make a loop to get them all.

codeforester
  • 39,467
  • 16
  • 112
  • 140
DoesJohnDo
  • 183
  • 1
  • 1
  • 5
  • Possible duplicate: http://stackoverflow.com/questions/371115/count-all-occurrences-of-string-in-lots-of-files-with-grep – mhinz Aug 21 '13 at 07:36

5 Answers5

27

You can say:

$ echo "field1;field2;field3;field4;" | grep -o ";" | wc -l
4

Alternatively,

$ tr -dc ';' <<< "field1;field2;field3;field4;" | wc -c
4

EDIT: In order to loop over the fields, you can say:

$ IFS=';' read -ra field <<< "field1;field2;field3;field4;"
$ for i in "${field[@]}"; do echo $i; done
field1
field2
field3
field4
devnull
  • 118,548
  • 33
  • 236
  • 227
  • It's working well. Just a short question. Am I doing it in the right way, if I want to get all fields, first recovering the number of fields, then with a loop FOR getting them all ? Or is there another functional command for that ? – DoesJohnDo Aug 21 '13 at 07:35
  • @DoesJohnDo If you want to simply loop, you don't need to get the number of fields. See edit above. – devnull Aug 21 '13 at 07:40
  • I see. However, I don't have the same nature of fields. I wrote field1, field2, etc. but it could have been Orange;Green;Bob;Table;. Do you have such convenient solution for that situation ? – DoesJohnDo Aug 21 '13 at 07:44
  • Never mind, your answer suits me well. No need to more loose your time. Thank you for your help. – DoesJohnDo Aug 21 '13 at 07:50
  • Note the last solution will give you an extra blank line at the end, because there's an `;`-separated blank field at the end of the line. You might want to drop trailing semicolons first. – glenn jackman May 24 '14 at 11:54
19

Using cut is not the recommended command to do the job as it can be done in one line using awk.

For e.g.

data='field1;field2;field3;field4;'
echo $data|awk -F';' '{print NF}'

Actually above will return 5 because in given data there is "semicolon" at the end, hence linux awk assumes that last field is empty.

But if this is expected to have it like this then you can use below command to subtract 1.

echo $data|awk -F';' '{print NF-1}'

Explanation: -F option in awk is for delimiter, in this case it's semicolon (enclosed in single quote) NF means Number of fields.

Shashilpi
  • 191
  • 2
8

Pure bash solution:

myStr='field1;field2;field3;field4;'
x="${myStr//[^;]}"                   # remove everything but the delimiter ';'
echo "${#x}"                         # get the length - outputs 4
codeforester
  • 39,467
  • 16
  • 112
  • 140
Aleks-Daniel Jakimenko-A.
  • 10,335
  • 3
  • 41
  • 39
1

The option I have provided still holds up without bugs or manipulation.

j=0
i=1

while [ "$j" ]
do 
    j="$(cat dataFile.txt | cut -d[delimiter] -f$i)" 
    i="$(($i+1))"
done 

echo "$(($i-2))"

I initialize a variable $j to hold the results, and as long as the results exist, the loop counts each run. cut always runs till 2 past the field, explaining my $(($i-2)) statement.

Put your data-file's delimiter after cut -d I used a comma to separate my fields, so my actual code has: cut -d, -f$i

I hope this makes sense.

David Pena
  • 11
  • 2
  • Your solution is not pure Bash at all: you're using the external programs `cat` and `cut`. And also, it's really ugly `:)`. – gniourf_gniourf May 24 '14 at 06:05
  • I'm excited that people actually read this! Why is my code ugly? Should I write the multi-line version? And I'm using BASH commands, rather than external programs like awk, a completely separate language set. Perhaps I have misinterpreted the difference between BASH, ZSH, Korn, and so forth, thinking that they each have their separate and often identical commands (rather than external programs) attached to their build. Perhaps I misunderstand what a BASH command is, and a BASH command is an external program. Wow, no spaces on comments. – David Pena May 24 '14 at 06:55
  • In your terminal, type `help`. You'll have the list of all Bash builtins and keywords. When we say _pure Bash_ we mean only builtin commands! `cat` and `cut` are _not_ builtins. These are external commands. – gniourf_gniourf May 24 '14 at 07:02
  • Right on for the info. I tried using the awk option in an actual bash script and it failed to produce accurate results. The option I contributed remains consistent. While fooling with it, I've found it to be a function of my string manipulations, silly strings. – David Pena May 24 '14 at 07:54
  • It's inefficient: you have to read the file N times to extract N fields. – glenn jackman May 24 '14 at 11:56
-1
#!/bin/bash
DATA="field1;field2;field3;field4;"
# save IFS
OLDIFS=$IFS
# set new IFS
IFS=";"
# set DATA as position parameters
set -- ${DATA//\*/\\*}
# simply get parameter count
echo "Fields: $#"
# restore IFS 
IFS=$OLDIFS
hàqk
  • 1
  • 2
  • This is a common antipattern: it's subject to pathname expansion, and hence may not give the correct answer. – gniourf_gniourf Mar 18 '16 at 17:58
  • Can you provide an example of when this would fail? – hàqk Mar 20 '16 at 13:11
  • `DATA='*;'`, run from a non empty directory… – gniourf_gniourf Mar 20 '16 at 13:29
  • Well, there are other glob characters: for example, `DATA='[hello];'` and if you happen to have files named `h`, `e`, `l` and `o`, then you'll get wrong results. In fact, people using this antipattern usually will use `set -f` to disable globbing. (At this point, I really feel that we're fighting against the shell instead of working with it). – gniourf_gniourf Mar 20 '16 at 19:22
  • In Bash, the canonical way to [split a string at a delimiter](http://stackoverflow.com/questions/918886/how-do-i-split-a-string-on-a-delimiter-in-bash/24426608#24426608) is to use `read`. So your code would be better written as: `IFS=';' read -r -d '' -a data_array < <(printf '%s\0' "$DATA"); echo "${#data_array[@]}"`. – gniourf_gniourf Mar 20 '16 at 19:25