How to iterate string with blank?

Viewed 78

I have next code:

line="95:p1=a b c 95:p2=d e 96:p1=a b c 96:p2=d e"

for l in $line; do
    echo $l
done

I got next:

95:p1=a
b
c
95:p2=d
e
96:p1=a
b
c
96:p2=d
e

But in fact a b c is a whole string in my business, so if possible I could get next with some ways?

95:p1=a b c
95:p2=d e
96:p1=a b c
96:p2=d e
5 Answers

1st solution: With your shown samples and attempts please try following awk code. Written and tested with GNU awk.

Here is the Online demo for used regex.

echo "$line"
95:p1=a b c 95:p2=d e 96:p1=a b c 96:p2=d e

awk -v RS='[0-9]{2}:p[0-9]=[a-zA-Z] [a-zA-Z]( [a-zA-Z]|$)*' 'RT{print RT}' <<<"$line"

Output with shown samples will be as follows:

95:p1=a b c
95:p2=d e
96:p1=a b c
96:p2=d e


2nd solution: With any POSIX awk please try following awk code:

awk '
{
  while(match($0,/[0-9]{2}:p[0-9]=[a-zA-Z] [a-zA-Z]( [a-zA-Z]|$)*/)){
    print substr($0,RSTART,RLENGTH)
    $0=substr($0,RSTART+RLENGTH)
  }
}
' <<<"$line"

You can't do this with regular parameters. If you want a collection of strings that can contain whitespace, use an array.

line=("95:p1=a b c" "95:p2=d e" "96:p1=a b c" "96:p2=d e")

for l in "${line[@]}"; do
    echo "$l"
done

Otherwise, you'll need some way of distinguishing between "literal" spaces and "delimiter" spaces. (Maybe the latter is followed by <num>:, but that logic is not trivial to implement using bash regular expressions. You would probably be better off using a more capable language instead of trying to do this in bash.)

With bash

read -ra f <<<"$line"    # split the string into words
n=${#f[@]}
i=0
lines=()
while ((i < n)); do
    l=${f[i++]}
    until ((i == n)) || [[ ${f[i]} =~ ^[0-9]+: ]]; do
        l+=" ${f[i++]}"
    done
    lines+=( "$l" )
done
declare -p lines

outputs

declare -a lines=([0]="95:p1=a b c" [1]="95:p2=d e" [2]="96:p1=a b c" [3]="96:p2=d e")

Now you can do

for l in "${lines[@]}"; do
    do_something_with "$l"
done

Or , and capture the lines with bash builtin mapfile

mapfile -t lines < <(sed -E 's/ ([0-9]+:)/\n\1/g' <<< "$line")
echo "${line}" | 

mawk 'BEGIN { FS=RS="^$"(ORS="") } gsub(" [^ :]+:","\1&") + gsub("\1.","\n")^_'
95:p1=a b c
95:p2=d e
96:p1=a b c
96:p2=d e

If your grep supports -P (PCRE) option, would you please try:

grep -Po "\d+:.*?(?=(?:\s*\d+:|$))" <<< "$line"

Output:

95:p1=a b c
95:p2=d e
96:p1=a b c
96:p2=d e

Explanation of the regex \d+:.*?(?=(?:\s*\d+:|$)):

  • \d+: matches digits followed by a colon. It will match 95: or 96:.
  • .*?(?=pattern) matches the shortest sequence of characters followd by the pattern. (?=pattern) is a lookahead assertion which is not included in the mathed result.
  • The pattern above is described as (?:\s*\d+:|$), an alternation of digits followed by a colon or end of the string. The former matches the starting portion of the next item. The \s* before \d+ matches a zero or more space character(s) which trims the whitespace(s) from the matched result.

If you want to iterate over the divided substrings, you can say:

while IFS= read -r i; do
    echo "$i"   # or whatever you want to do with "$i"
done < <(grep -Po "\d+:.*?(?=(?:\s*\d+:|$))" <<< "$line")
Related