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
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
Use sed as follows:
$ echo "foobarbazblargblurg" | sed 's/.{4}/& /g'
foob arba zbla rgbl urg
Method 2
You can use the following simple example:
$ echo "foobarbazblargblurg" | fold -w4 | paste -sd' ' - foob arba zbla rgbl
Method 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 '%sn' "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="bb9ffb">[email protected]</a>" || printf '%sn' $(< /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%ccould 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
printfreuses 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 variablemyvar(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.
Method 4
Here is the example using grep and xargs:
$ echo "foobarbazblargblurg" | grep -o .... | xargs foob arba zbla rgbl
Method 5
With zsh only:
str=foobarbazblargblurg
set -o extendedglob
printf '%sn' ${str//(#m)????/$MATCH }
Or
printf '%s%s%s%s ' ${(s::)str}
with ksh93 only:
printf '%sn' "${str//????/ }"
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 '%sn' "$out"
Now, that’s for characters. If you wanted to do it on grapheme clusters (for instance, to break Stéphane, written as $'Steu0301phane', as Stép hane and not Ste phan e), with zsh:
set -o rematchpcre
str=$'Steu301phane' out=
while [[ $str =~ '(X{4})(.+)' ]] {
out+="$match[1] " str=$match[2]
}
out+=$str
printf '%sn' $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=$'Steu301phane' out=
while
start=${ printf %L.4s. "$str"; }
start=${start%.}
[ "$start" != "$str" ]
do
out+="$start " str=${str#"$start"}
done
out+=$str
printf '%sn' "$out"
Method 6
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 $'aabbnc 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)
Method 7
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
Method 8
You can also try the split command. While it is commonly used to split files, you can use it for this as well:
split -b 4 --filter='cat;echo -n " "' <<< 'foobarbazblargblurg'
I mean it works, but YMMV.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0