149

How can I replace spaces with new lines on an input like:

/path/to/file /path/to/file2 /path/to/file3 /path/to/file4 /path/to/file5 etc...

To obtain the following:

/path/to/file
/path/to/file2
/path/to/file3
/path/to/file4
/path/to/file5

Note

I'm posting this question to help other users, it was not easy to find a useful answer on UNIX SE until I started to type this question. After that I found the following:

Related question

How can I find and replace with a new line?

jubilatious1
  • 3,195
  • 8
  • 17
laconbass
  • 4,409
  • 4
  • 17
  • 20

13 Answers13

236

Use the tr command

echo "/path/to/file /path/to/file2 /path/to/file3 /path/to/file4 /path/to/file5" \
| tr " " "\n"

Found on http://www.unix.com/shell-programming-scripting/67831-replace-space-new-line.html

laconbass
  • 4,409
  • 4
  • 17
  • 20
  • 1
    tr was my first thought as well, although there are so many ways to do it! This is pretty much EXACTLY what tr is for, though! – Rob Dec 17 '13 at 15:43
  • 9
    +1, but as a comment: That does not work if the filenames contain spaces themselves – Bonsi Scott Dec 17 '13 at 16:10
  • 2
    @BonsiScott If the filenames contain spaces, the whole exercise becomes pointless. – Kusalananda Apr 14 '22 at 06:46
  • 1
    I think it's worth noting that the sequence \n represents a newline because... it's in the documentation. I mean : when writing '\n' (or "\n") in the shell, tr and sed do not see an actual newline, they see "a backslash then the letter n". In fact, giving an actual newline (can be obtained with $'\n' in bash) would work with tr (e.g. tr " " $'\n' works) but not with sed (e.g. sed $'s/ /\n/g' does not work) – YoungFrog Jul 14 '22 at 12:58
  • 2
    @YoungFrog sed $'s/ /\\n/g'. A literal newline has to be escaped with a backslash. But sed 'y/ /\n/' would be faster and also more portable. – Kusalananda Aug 24 '22 at 11:26
32

In this case I would use printf:

printf '%s\n' /path/to/file /path/to/file2 /path/to/file3 /path/to/file4 /path/to/file5

If there are spaces within the one of the paths, you can quote that filepath in order to prevent it from being split on the spaces:

printf '%s\n' /path/to/file '/path/to/file with spaces' /path/to/another/file

To transform text in general, tr is your best bet, as covered in an existing answer.

evilsoup
  • 6,807
  • 3
  • 34
  • 40
  • 1
    A quick note from the future: thanks for posting this solution, it's been very reliable for me, compared to using "echo". – Liesmith Apr 30 '15 at 21:48
17

Be pragmatic, use GNU sed!!

    sed -i 's/\s\+/\n/g' file

The above says to substitute one or more whitespace characters (\s\+) with a newline (\n).

The -i parameter stands for changing the file "in place".

This is more or less "substitute /one space or more/ for /newline/ globally".

MGP
  • 582
  • 1
    If you have multiple spaces between text then you will get blank lines. You need to add a '+' after the \s to indicate that you want to match at least one spaces as apposed to exactly one space. ie. sed 's/\s+/\n/g' –  Dec 17 '13 at 20:43
  • Does it work for '/path/to/file with spaces – Timo Jan 02 '21 at 20:12
  • 1
    @Timo no, every space will be translated into a linebreak, you could use / as the break point and then inject the / again but that'll be buggy with some edge cases like /path/with space before slash /and then more. – MGP Jan 03 '21 at 14:50
  • 1
    use -i to make changes to original file. so sed 's/\s\+/\n/g' file -i – AhmFM Nov 24 '21 at 01:53
13

Assuming you have a string with spaces as separators:

    newline_separated=${space_separated// /$'\n'}

However, you're probably asking the wrong question. (Not necessarily, for example this might come up in a makefile.) A space-separated list of file names does not really work: what if one of the file names contained spaces?

If a program receives file names as arguments, don't join them with spaces. Use "$@" to access them one by one. Although echo "$@" prints the arguments with spaces in between, that's due to echo: it prints its arguments with spaces as separators. somecommand "$@" passes the file names as separate arguments to the command. If you want to print the arguments on separate lines, you can use

    printf '%s\n' "$@"

If you do have space-separated file names and you want to put them in an array to work on them, you can use an unquoted variable expansion to split the value at characters on IFS (you'll need to disable wildcard expansion with set -f, otherwise glob patterns will be expanded in the value):

    space_separated_list='/path/to/file1 /path/to/file2 /path/to/file3'
    IFS=' '; set -f
    eval "array=(\$space_separated_list)"
    for x in "${array[@]}"; do …

You can encapsulate this in a function that restores the -f setting and the value of IFS when it is done:

    split_list () {
      local IFS=' ' flags='+f'
      if [[ $- = *f* ]]; then flags=; fi
      set -f
      eval "$1=(\$2)"
      set $flags
    }
    split_list array '/path/to/file1 /path/to/file2 /path/to/file3'
    for x in "${array[@]}"; do …
  • 1
    Changing the correct answer to this one. I think it's more correct to research how I got to a space-separated list, and retrieve that information in another way.

    It would be cool for learners like me if you comment a bit your snipnet, telling us what do things like the local var IFS or the condition $- = f do. That kind of things a person that uses bash few times don't knows.

    – laconbass Dec 11 '14 at 22:01
  • @laconbass I've added a short explanation. Look up set -f and IFS in the bash manual if you want all the details. See also http://unix.stackexchange.com/questions/16192/what-is-ifs-in-context-of-for-looping/16195#16195 – Gilles 'SO- stop being evil' Dec 11 '14 at 22:26
  • @Guilles That short explanation is enough for me, and i think it will be for many others. Ty for your time – laconbass Dec 12 '14 at 02:31
10

As an alternative to tr from @laconbass, you can also use xargs in this case:

echo "/path/to/file /path/to/file2 /path/to/file3 /path/to/file4  /path/to/file5"\
    | xargs -n1

The advantage is that it works even with multiple whitespaces, which tr doesn't.

FranMowinckel
  • 241
  • 2
  • 4
  • 1
    It would work with multiple spaces between paths, certainly, but like most of the other answered here it does not handle spaces in the path names – Chris Davies Oct 22 '21 at 07:38
  • @ChrisDavies Yes it does so long as you put the paths with spaces in inner quotes. Example: echo "/path/to/file '/path/to/my file'" | xargs -n1 – Alvin Thompson Feb 27 '24 at 18:28
4

The question on the title: replace space with new line

The simple, quick, brute force solution is to do exactly that, replace all spaces with new lines:

echo "$input" | tr ' ' '\n'
echo "$input" | sed 'y/ /\n/'
echo "$input" | sed 's/ /\n/g'

Filenames

But in your question you are listing a list of paths:

echo '/path/to/file /path/to/file2 /path/to/file3 /path/to/file4 /path/to/file5' > test.txt

Using the solution above will not work for filenames with spaces (or newlines).

Better

But we can use a two characters delimiter: / (space slash)
That pair of characters could only exist at the beginning of a new (absolute) path:

input='/path/to/file /path/to/file2 /path/to/one space /path/to/new
line /path/to/file5'

$ printf '%s\n' "$input" | sed 's# /#\n/#g'

/path/to/file /path/to/file2 /path/to/one space /path/to/new line /path/to/file5

For relative paths, we need to also allow paths that start with ./ or ../:

$ cat test2.txt 
/path/to/file /path/to/file2 /path/to/one space /path/to/new
line /path/to/file5 ./path/to/file ../path/to/file2 ./path/to/one space ./path/to/new
line ./path/to/file5 ../path/to/file ../path/to/file2 ../path/to/one space ../path/to/new
line ../path/to/file5

$ sed 's# ([.]{0,2}/)#\n\1#g' test2.txt /path/to/file /path/to/file2 /path/to/one space /path/to/new line /path/to/file5 ./path/to/file ../path/to/file2 ./path/to/one space ./path/to/new line ./path/to/file5 ../path/to/file ../path/to/file2 ../path/to/one space ../path/to/new line ../path/to/file5

Best

For path names with newlines it is better to quote each pathname.

$ sed -e '1s/^/"/' -e 's# \([.]\{0,2\}/\)#"\n"\1#g' -e '$s/$/"/' test2.txt

"/path/to/file" "/path/to/file2" "/path/to/one space" "/path/to/new line" "/path/to/file5" "./path/to/file" "../path/to/file2" "./path/to/one space" "./path/to/new line" "./path/to/file5" "../path/to/file" "../path/to/file2" "../path/to/one space" "../path/to/new line" "../path/to/file5"

  • "More best", change the way the list of file names was captured in the first place, so that either no splitting is required or that an otherwise unused character separates the items – Chris Davies Oct 22 '21 at 07:39
  • Nice answer. Here's a slight variation of your best solution using perl to make the output NUL delimited (in case the filenames contain quote characters): perl -pe 's/\s+([.]{0,2}\/)/\0\1/g' test2.txt – Robin A. Meade Oct 22 '21 at 08:09
1

Here is how I did it:

echo "/path/to/file /path/to/file2 /path/to/file3 /path/to/file4 /path/to/file5" | sed 's/ /\
'/g

Notice the use of Enter key after backslash in the sed command.

unxnut
  • 6,008
  • what prons and cons has sed over tr? – laconbass Dec 17 '13 at 15:33
  • Your comfort level :-) I think you can do more with sed as it is an editor rather than simply translate characters. – unxnut Dec 17 '13 at 15:38
  • 5
    sed can do so much more, but is totally overkill for this. tr is the right tool for THIS job, but knowledge of sed and regexes will certainly come in handy later! – Rob Dec 17 '13 at 15:44
1

option1:

echo "/path/to/file /path/to/file2 /path/to/file3 /path/to/file4 /path/to/file5"|perl -pne  "s/ /\n/g"

option2:

echo "/path/to/file /path/to/file2 /path/to/file3 /path/to/file4 /path/to/file5"|awk '{gsub(/ /,"\n",$0);print }'```

output

/path/to/file
/path/to/file2
/path/to/file3
/path/to/file4
/path/to/file5

0

Another approach, assuming the line is in a variable called line:

for path in $line;do echo $path;done

This makes use of the fact that Bash splits its arguments on whitespace by default and that echo appends a newline to its input by default.

Joseph R.
  • 39,549
0
echo word1 word2 ... | sed -e 'y/ /\n/;P;D'

is another method to turn single-space-separated words into newline separated.

0

Using Raku (formerly known as Perl_6)

Sample Input:

/path/to/file1 /path/to/file2 /path/to/file3

Split on whitespace (before forwardslash):

~$ raku -ne '.put for .split(/ \s <?before "/">/);'  file

Sample Output:

/path/to/file1
/path/to/file2
/path/to/file3

This is just to get you started. What's nice is that if you just add .IO to the code above, Raku's object system from this point will recognize IO::Path objects, including the correct path separator (backslash for Windows). The objective is to make your code more portable from platform to platform. Below results from adding .IO and change the put call to say:

~$ raku -ne '.IO.say for .split(/ \s <?before "/">/);'  file 
"/path/to/file".IO
"/path/to/file2".IO
"/path/to/file3".IO

Reading your file containing the list of files, and extracting the filenames, a.k.a. basename:

~$ raku -ne '.IO.say for .split(/ \s <?before "/">/);'  file 
file1
file2
file3

Finally, you can output the contents of the first file, by slurping in the [0]th element. Assuming these are relative paths, from the directory you're in on the command line you use ~ tilde to concatenate the path either with a leading . dot, or with $*CWD:

raku -ne '.map($*CWD ~ *).IO.slurp.put for .split(/ \s <?before "/">/)[0];'
#returns output of first file in list (assumes relative paths)

https://docs.raku.org/type/IO::Path
https://raku.org

jubilatious1
  • 3,195
  • 8
  • 17
-1

The following script is easy to understand and easy to use.

cat filename | tr ' ' '\n' | tee filename
Anthon
  • 79,293
  • 2
    The essence of your answer (tr) is the same as in the accepted answer. Beside that your answer has following issues: a) the command is not indented with 4 spaces (-> markdown layout) b) Your use of cat is useless (you can replace it with < filename tr ' ' '\n'). c) Your output filename is the same as the input filename. With that you create a data race. – maxschlepzig Sep 03 '15 at 06:26
  • 1
    apart from maxschlepzig's correct analysis, this is not a script, but a bunch of connected commands that have a name hardcoded (as script would hopefully have a header specifying the shell to use and use two parameters to specify in and output). Apart from that 2 out of 3 people in my household do not find it easy to understand. Maybe that is is not representative, but keep in mind that something you yourself understand (or in this case think you understand) always seems easy. – Anthon Sep 03 '15 at 06:34
-1

Here an example

list=$(cat index.txt | grep href=)
for iterator in $list
do
       echo $iterator
done

Here I have string with many links and space between every link, when you use loops it will solve your problem

Chris Davies
  • 116,213
  • 16
  • 160
  • 287