How can I split a word's letters, with each letter in a separate line?
For example, given "StackOver"
I would like to see
S
t
a
c
k
O
v
e
r
I'm new to bash so I have no clue where to start.
How can I split a word's letters, with each letter in a separate line?
For example, given "StackOver"
I would like to see
S
t
a
c
k
O
v
e
r
I'm new to bash so I have no clue where to start.
I would use grep:
$ grep -o . <<<"StackOver"
S
t
a
c
k
O
v
e
r
or sed:
$ sed 's/./&\n/g' <<<"StackOver"
S
t
a
c
k
O
v
e
r
And if empty space at the end is an issue:
sed 's/\B/&\n/g' <<<"StackOver"
All of that assuming GNU/Linux.
You may want to break on grapheme clusters instead of characters if the intent is to print text vertically. For instance with a e with an acute accent:
With grapheme clusters (e with its acute accent would be one grapheme cluster):
$ perl -CLAS -le 'for (@ARGV) {print for /\X/g}' $'Ste\u301phane'
S
t
é
p
h
a
n
e
(or grep -Po '\X' with GNU grep built with PCRE support)
With characters (here with GNU grep):
$ printf '%s\n' $'Ste\u301phane' | grep -o .
S
t
e
p
h
a
n
e
fold is meant to break on characters, but GNU fold doesn't support multi-byte characters, so it breaks on bytes instead:
$ printf '%s\n' $'Ste\u301phane' | fold -w 1
S
t
e
�
�
p
h
a
n
e
On StackOver which only consists of ASCII characters (so one byte per character, one character per grapheme cluster), all three would give the same result.
With many awk versions
awk -F '' -v OFS='\n' '{$1=$1};1' <<<'StackOver'
You can use the fold (1) command. It is more efficient than grep and sed.
$ time grep -o . <bigfile >/dev/null
real 0m3.868s
user 0m3.784s
sys 0m0.056s
$ time fold -b1 <bigfile >/dev/null
real 0m0.555s
user 0m0.528s
sys 0m0.016s
$
One significant difference is that fold will reproduce empty lines in the output:
$ grep -o . <(printf "A\nB\n\nC\n\n\nD\n")
A
B
C
D
$ fold -b1 <(printf "A\nB\n\nC\n\n\nD\n")
A
B
C
D
$
You can handle multibyte characters like:
<input \
dd cbs=1 obs=2 conv=unblock |
sed -e:c -e '/^.*$/!N;s/\n//;tc'
Which can be pretty handy when you're working with live input because there's no buffering there and a character is printed as soon it is whole.
The below will be generic:
$ awk -F '' \
'BEGIN { RS = ""; OFS = "\n"} {for (i=1;i<=NF;i++) $i = $i; print }' <file_name>
Since you specifically asked for an answer in bash, here's a way to do it in pure bash:
while read -rn1; do echo "$REPLY" ; done <<< "StackOver"
Note that this will catch the newline at the end of the "here document". If you want to avoid that, but still iterate over the characters with a bash loop, use printf to avoid the newline.
printf StackOver | while read -rn1; do echo "$REPLY" ; done
You may use word boundaries also..
$ perl -pe 's/(?<=.)(\B|\b)(?=.)/\n/g' <<< "StackOver"
S
t
a
c
k
O
v
e
r
In bash:
This works with any text and with only bash internals (no external utility called), so, should be fast on short strings.
str="StackOvér áàéèëêếe"
[[ $str =~ ${str//?/(.)} ]] # Use a regex to split.
printf '%s\n' "${BASH_REMATCH[@]:1}" # Print all characters.
Output:
S
t
a
c
k
O
v
é
r
á
à
é
è
ë
ê
ế
e
s=stackoverflow;
$ time echo $s | fold -w1
s
t
a
c
k
o
v
e
r
real 0m0.014s
user 0m0.000s
sys 0m0.004s
updates here is the hacky|fastest|pureBashBased way !
$ time eval eval printf \'%s\\\\n\' \\\${s:\{0..$((${#s}-1))}:1}
s
t
a
c
k
o
v
e
r
real 0m0.001s
user 0m0.000s
sys 0m0.000s
for more awesomeness
function foldh ()
{
if (($#)); then
local s="$@";
eval eval printf \'%s\\\\n\' \\\"\\\${s:\{0..$((${#s}-1))}:1}\\\";
else
while read s; do
eval eval printf \'%s\\\\n\' \\\"\\\${s:\{0..$((${#s}-1))}:1}\\\";
done;
fi
}
function foldv ()
{
if (($#)); then
local s="$@";
eval eval echo \\\"\\\${s:\{0..$((${#s}-1))}:1}\\\";
else
while read s; do
eval eval echo \\\"\\\${s:\{0..$((${#s}-1))}:1}\\\";
done;
fi
}
read -a var <<< $(echo "$yourWordhere" | grep -o "." | tr '\n' ' ')
this will split your word and store it in array var.
for x in $(echo "$yourWordhere" | grep -o '.')
do
code to perform operation on individual character $x of your word
done
On bash 4.2 and up (I tested 4.2.46 and 5.1), with extglobs you can use and empty "zero or one" match:
# shopt -s extglob
# V="StackOverflow"
# echo -e ${V//?()/\\n}
S
t
a
c
k
O
v
e
r
f
l
o
w
It also works to split your string into an array:
# A=( ${V//?()/ } )
# declare -p A
declare -a A='([0]="S" [1]="t" [2]="a" [3]="c" [4]="k" [5]="O" [6]="v" [7]="e" [8]="r" [9]="f" [10]="l" [11]="o" [12]="w")'
Using Raku (formerly known as Perl_6)
~$ echo "StackOvér áàéèëêếe" | raku -ne '.chars.put;'
18
~$ echo "StackOvér áàéèëêếe" | raku -ne '.put for .comb;'
S
t
a
c
k
O
v
é
r
á
à
é
è
ë
ê
ế
e