21

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.

16 Answers 16

32

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.

14
  • grep -o . <<< ¿¿¿ .. -o searches for the PATTERN provided right? and what it does here in your command? Jan 5 '16 at 0:06
  • 1
    @jimmij I cant find any help on what <<< really does! any help? Jan 5 '16 at 10:50
  • 3
    @SijaanHallak This is so called Here string, grosso modo equivalent of echo foo | ... just less typing. See tldp.org/LDP/abs/html/x17837.html
    – jimmij
    Jan 5 '16 at 11:02
  • 1
    @SijaanHallak change . to \B (doesn't match on word boundary).
    – jimmij
    Jan 5 '16 at 17:40
  • 1
    @SijaanHallak - you can drop the second sed like: sed -et -e's/./\n&/g;//D'
    – mikeserv
    Jan 6 '16 at 6:30
20

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.

6
  • I'm surprised grep -Po doesn't do what one would expect (like grep -P does).
    – jimmij
    Jan 5 '16 at 0:19
  • @jimmij, what do you mean? grep -Po . finds characters (and a combining acute accent following a newline character is invalid), and grep -Po '\X' finds graphem clusters for me. You may need a recent version of grep and/or PCRE for it to work properly (or try grep -Po '(*UTF8)\X') Jan 5 '16 at 0:23
  • 3
    @SijaanHallak These might be helpful: joelonsoftware.com/articles/Unicode.html, eev.ee/blog/2015/09/12/dark-corners-of-unicode
    – jpmc26
    Jan 5 '16 at 21:55
  • Are you claiming that $'e\u301' is equivalent/equal to é ?
    – ImHere
    Jun 15 at 17:20
  • @Isaac, no, I'm not claiming any such thing though there are some definitions of "equivalent" for which that would be true. Jun 15 at 17:22
7

If you have perl6 in your box:

$ perl6 -e 'for @*ARGS -> $w { .say for $w.comb }' 'cường'       
c
ư
ờ
n
g

work regardless of your locale.

0
6

With many awk versions

awk -F '' -v OFS='\n' '{$1=$1};1' <<<'StackOver'
1
  • Great! But on my version of nAWK ("One True AWK") that doesn't work. However this does the trick: awk -v FS='' -v OFS='\n' '{$1=$1};1' (wondering if that's more portable since -F '' might yield the ERE: //)
    – eruve
    Feb 4 '19 at 6:48
5
echo StackOver | sed -e 's/./&\n/g'
S
t
a
c
k
O
v
e
r
1
  • This won't help as it prints a new line at the end Jan 5 '16 at 10:56
5

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
$ 
4

The below will be generic:

$ awk -F '' \
   'BEGIN { RS = ""; OFS = "\n"} {for (i=1;i<=NF;i++) $i = $i; print }' <file_name>
4

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
0
4

Also Python 2 can be used from the command line:

python <<< "for x in 'StackOver':
   print x"

or:

echo "for x in 'StackOver':
    print x" | python

or (as commented by 1_CR) with Python 3:

python3 -c "print(*'StackOver',sep='\n')"
0
3

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.

7
  • 1
    NP, should we add a note about the locale?
    – cuonglm
    Jan 5 '16 at 9:35
  • Does not work for combining characters like Stéphane Chazelas answer, but with proper normalization this should not matter.
    – kay
    Jan 5 '16 at 13:06
  • @Kay - it's works for combining characters if you want it to - that's what sed scripts are for. i'm not likely to write one right about now - im pretty sleepy. it's really useful, though, when reading a terminal.
    – mikeserv
    Jan 5 '16 at 14:30
  • @cuonglm - if you like. it should just work for the locale, given a sane libc, though.
    – mikeserv
    Jan 5 '16 at 14:33
  • Note that dd will break multibyte characters, so the output will not be text anymore so the behaviour of sed will be unspecified as per POSIX. Jan 5 '16 at 22:09
3

You may use word boundaries also..

$ perl -pe 's/(?<=.)(\B|\b)(?=.)/\n/g' <<< "StackOver"
S
t
a
c
k
O
v
e
r
0
2

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
1
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
}
6
  • Will this ever give different results to fold -b1 ?
    – JigglyNaga
    Jul 25 '16 at 12:10
  • since each byte have a width=1 the result will be the same !
    – Yunus
    Jul 25 '16 at 12:30
  • 1
    So how is this not a duplicate of the earlier answer?
    – JigglyNaga
    Jul 25 '16 at 13:17
  • because it shows tha same cmd with different argyment , and that is nice to know .
    – Yunus
    Jul 25 '16 at 13:45
  • 1
    An eval could be a big risk, a double eval is even more risky. Specially with arbitrary input from $s. Just saying !!
    – ImHere
    Jun 15 at 17:05
1
read -a var <<< $(echo "$yourWordhere" | grep -o "." | tr '\n' ' ')

this will split your word and store it in array var.

1
for x in $(echo "$yourWordhere" | grep -o '.')
do
    code to perform operation on individual character $x of your word
done
1

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

https://raku.org/

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.