147

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?

4

13 Answers 13

233

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

5
  • 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, 2013 at 15:43
  • 8
    +1, but as a comment: That does not work if the filenames contain spaces themselves Dec 17, 2013 at 16:10
  • 2
    @BonsiScott If the filenames contain spaces, the whole exercise becomes pointless.
    – Kusalananda
    Apr 14, 2022 at 6: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, 2022 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, 2022 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.

1
  • 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, 2015 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".

4
  • 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'
    – user14755
    Dec 17, 2013 at 20:43
  • Does it work for '/path/to/file with spaces
    – Timo
    Jan 2, 2021 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 3, 2021 at 14:50
  • 1
    use -i to make changes to original file. so sed 's/\s\+/\n/g' file -i
    – AhmFM
    Nov 24, 2021 at 1: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 …
3
  • 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, 2014 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 unix.stackexchange.com/questions/16192/… Dec 11, 2014 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, 2014 at 2:31
8

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.

1
  • 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 Oct 22, 2021 at 7:38
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"

2
  • "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 Oct 22, 2021 at 7: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 Oct 22, 2021 at 8: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.

3
  • what prons and cons has sed over tr?
    – laconbass
    Dec 17, 2013 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, 2013 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, 2013 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.

1
  • I tried this on my ubuntu system before i posted this question and couldn't get it to work.
    – laconbass
    Dec 17, 2013 at 16:38
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

-1

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

cat filename | tr ' ' '\n' | tee filename
2
  • 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. Sep 3, 2015 at 6: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 3, 2015 at 6: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

1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .