30

I've got a long line that I want to insert a space every 4 characters, on a single lone line of solid text to make it easier to read, what's the simplest way to do this? also I should be able to input the line from a pipe. e.g.

echo "foobarbazblargblurg" | <some command here>

gives

foob arba zbla rgbl urg
53

Use sed as follows:

$ echo "foobarbazblargblurg" | sed 's/.\{4\}/& /g'
foob arba zbla rgbl urg
  • 1
    cursing that was so close to the sed I tried first I could kick myself. – xenoterracide Jan 17 '11 at 14:29
  • 7
    Just curious, what's the '&' accomplish? Oh, it's a standin for 'the thing what just matched'. Silly me. – Omnifarious Aug 27 '12 at 5:25
  • 1
    it should be noted that this adds a space at the end as well if there is one more character in the string, which might not be desirable – Anubis Apr 19 '18 at 17:07
  • @Anubis 's/.\{4\}/& /g;s/ $//' – wieczorek1990 May 23 at 15:21
17

You can use the following simple example:

$ echo "foobarbazblargblurg" | fold -w4 | paste -sd' ' -
foob arba zbla rgbl
  • Very nice...I think this is even better than the sed answer. I didn't know about fold before. – Wildcard Jan 28 '16 at 19:18
  • 1
    Unfortunately, with current versions of GNU fold, it doesn't work with multi-byte characters (as in echo €€€€€€€€ | fold -w4 | paste -sd' ' - in UTF-8). – Stéphane Chazelas May 5 '17 at 15:26
3

Here is the example using grep and xargs:

$ echo "foobarbazblargblurg" | grep -o .... | xargs
foob arba zbla rgbl
  • xargs runs echo by default, so it won't work with words like -nen or that contain backslashes depending on the echo implementation. You'll see the odd newline character once in a while as well if xargs runs more than one echo. Better to pipe to paste -sd ' ' - instead. Note that -o is not a standard option. – Stéphane Chazelas May 5 '17 at 15:36
3

In bash only, no external commands:

str="foobarbazblargblurg"
[[ $str =~ ${str//?/(.)} ]]
printf "%s%s%s%s " "${BASH_REMATCH[@]:1}"

or as a one-line pipe version:

echo foobarbazblargblurg | 
  { IFS= read -r str; [[ $str =~ ${str//?/(.)} ]]; \
    printf "%s%s%s%s " "${BASH_REMATCH[@]:1}"; }

The way this works is to convert each character of the string to a "(.)" for regex match and capture with =~, then just output the captured expressions from BASH_REMATCH[] array, grouped as required. Leading/trailing/intermediate spaces are preserved, remove the quotes around "${BASH_REMATCH[@]:1}" to omit them.

Here it is wrapped up in a function, this one will process its arguments or read stdin if there are no arguments:

function fmt4() {
  while IFS= read -r str; do
    [[ $str =~ ${str//?/(.)} ]]
    printf "%s%s%s%s " "${BASH_REMATCH[@]:1}"
  done < <( (( $# )) && printf '%s\n' "$@" || printf '%s\n' $(< /dev/stdin) )
}

$ echo foobarbazblargblurg | fmt4
foob arba zbla rgbl urg 

You can easily parameterise the count to adjust the format string accordingly.

A trailing space is added, use two printfs instead of one if that's a problem:

printf "%s%s%s%s" "${BASH_REMATCH[@]:1:4}"
(( ${#BASH_REMATCH[@]} > 5 )) && printf " %s%s%s%s" "${BASH_REMATCH[@]:5}"

The first printf prints (up to) the first 4 characters, the second conditionally prints all the rest (if any) with a leading space to separate the groups. The test is for 5 elements not 4 to account for the zeroth element.

Notes:

  • shell printf's%c could be used instead of %s, %c (maybe) makes the intent clearer, but it's not multi-byte character safe. If your version of bash is capable, the above is all multi-byte character safe.
  • shell printf reuses its format string until it runs out of arguments, so it just gobbles up 4 arguments at a time, and handles the trailing arguments (so no edge cases needed, unlike some of the other answers here which are arguably wrong)
  • BASH_REMATCH[0] is the entire matched string, so only output starting from index 1
  • use printf -v myvar ... instead to store to a variable myvar (subject to usual read-loop/subshell behaviour)
  • add printf "\n" if required

You can make the above work in zsh if you use the array match[] instead of BASH_REMATCH[], and subtract 1 from all the indexes as zsh doesn't keep a 0 element with the entire match.

3

With zsh only:

str=foobarbazblargblurg

set -o extendedglob
printf '%s\n' ${str//(#m)????/$MATCH }

Or

printf '%s%s%s%s ' ${(s::)str}

with ksh93 only:

printf '%s\n' "${str//????/\0 }"

With any POSIX shell only (also avoiding the trailing space if the input length is a multiple of 4):

out=
while true; do
  case $str in
    (?????*)
      new_str=${str#????}
      out=$out${str%"$new_str"}' '
      str=$new_str
      ;;
    (*)
      out=$out$str
      break
  esac
done
printf '%s\n' "$out"

Now, that's for characters. If you wanted to do it on grapheme clusters (for instance, to break Stéphane, written as $'Ste\u0301phane', as Stép hane and not Ste phan e), with zsh:

set -o rematchpcre
str=$'Ste\u301phane' out=
while [[ $str =~ '(\X{4})(.+)' ]] {
  out+="$match[1] " str=$match[2]
}
out+=$str
printf '%s\n' $out

With ksh93, you could break by display width as well, which would work for that Stéphane above, but could also help when some other sorts of zero-width or double-width characters are involved:

str=$'Ste\u301phane' out=
while
  start=${ printf %L.4s. "$str"; }
  start=${start%.}
  [ "$start" != "$str" ]
do
  out+="$start " str=${str#"$start"}
done
out+=$str
printf '%s\n' "$out"
2

I'm going to answer by only inserting spaces as required so a space appears at least after every 4 characters on a line; not sure which way you want to handle this case. For example, given input of "aa bbccdd", you'd get output "aa bbcc dd" rather than "aa b bccd d".

I'm using Perl for lookahead, but I'm not very familiar with Perl in general, so there may be tweaks needed:

$ echo "foobarbazblargblurg" | perl -wp -e 's/[^ ]{4}(?=[^\n ])/$& /g'
foob arba zbla rgbl urg

$ echo 'aa bbccdd' | perl -wp -e 's/[^ ]{4}(?=[^\n ])/$& /g'
aa bbcc dd
# not 'aa b bccd d'!

$ echo 'some input' | perl -wp -e 's/[^ ]{4}(?=[^\n ])/$& /g'
some inpu t
# not 'some  inp ut'!

$ echo $'aabb\nc cddee' | perl -wp -e 's/[^ ]{4}(?=[^\n ])/$& /g' | 
> while read; do echo "${REPLY}x"; done
aabbx
c cdde ex
# no spaces added at the end of the first line (while loop to add to the end of
# the line and show this)
0

I have done this by using python

First i am reading file then i am dividing by 4 characters and adding space

#!/usr/bin/python
import re
b=re.compile(r'[a-z]{4}')

p=open('/root/l.txt','r')
i=p.readlines()
for j in i:
    m=re.findall(b,j)
print " " .join (m) + "  "

/root/l.txt==> Consists of the content which you given in example

output

foob arba zbla rgbl

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.