85

I cannot figure out how to list the various paths in $PATH separately so that they look like this:

/bin
/usr/bin
/usr/local/bin

How can this be done?

4

17 Answers 17

115

You can do this with any one of the following commands, which substitutes all occurrences of : with new lines \n.

sed:

$ sed 's/:/\n/g' <<< "$PATH"

tr:

$ tr ':' '\n' <<< "$PATH"

python:

$ python -c 'import sys;print(sys.argv[1].replace(":","\n"))' "$PATH"
4
  • 1
    Variation on your python approach: python -c 'import sys;print sys.argv[1].replace(":","\n")' $PATH Oct 15, 2016 at 19:02
  • Another Python variation, kinda hacky: python -c "print r'$PATH'.replace(':', '\n')" (using a raw string in case of backslashes)
    – wjandrea
    Apr 8, 2018 at 2:05
  • 3
    tr worked for me (on mac, btw). Thanks.
    – Mike S.
    Aug 16, 2018 at 16:15
  • For those, like me, who want to add it to their .bash_profile, add it like this: alias path='tr ":" "\n" <<< "$PATH"'
    – Mike S.
    Aug 16, 2018 at 16:29
93

Use bash's Parameter Expansion:

echo "${PATH//:/$'\n'}"

This replaces all : in $PATH by a newline (\n) and prints the result. The content of $PATH remains unchanged.
If you only want to replace the first :, remove second slash: echo -e "${PATH/:/\n}"

13
  • 5
    I think that this is the best solution because it's done in pure bash. Mar 22, 2015 at 22:43
  • 1
    @ryanmjacobs mildly curious: what do you think my solution uses? :D
    – muru
    Mar 23, 2015 at 6:27
  • 4
    Follow link (Parameter Expansion) in my answer and scroll down to last second section called: ${parameter/pattern/string}
    – Cyrus
    Mar 23, 2015 at 19:18
  • 3
    @DominicCronin: For the meaning of $'\n' see: ANSI-C Quoting
    – Cyrus
    Oct 13, 2019 at 4:52
  • 2
    This works for me on ZSH: echo "${PATH//:/\n}"
    – Slim
    Nov 29, 2020 at 21:12
31

Using IFS:

(set -f; IFS=:; printf "%s\n" $PATH)

IFS holds the characters on which bash does splitting, so an IFS with : makes bash split the expansion of $PATH on :. printf loops the arguments over the format string until arguments are exhausted. We need to disable globbing (wildcard expansion) using set -f so that wildcards in PATH directory names don't get expanded.

2
  • 2
    This works correctly with backslashes and spaces! Mar 23, 2015 at 12:33
  • Splits also on spaces in bash terminal on Windows
    – David
    Jul 18, 2022 at 13:09
17

Using xargs:

xargs -n1 -d: <<< $PATH

From man xargs

-n max-args
          Use  at  most  max-args  arguments per command line.

 -d delim
          Input  items  are terminated by the specified character.
2
  • would variation on this, such as echo $PATH | xargs -n1 -d: echo be redundant or it doesn't matter ? Apr 3, 2015 at 7:59
  • 1
    @Serg echo $PATH | xargs -n1 -d: will do the same thing for you but you will be using one more shell. The first one will evaluate echo $PATH and pipe output to next shell to do the rest.
    – sourav c.
    Apr 7, 2015 at 12:46
11

Probably the only way that hasn't been mentioned is the way i've been using it for years:

echo $PATH | tr ":" "\n"

so, in your .profile or .bash_profile or whatever, you can add:

alias path='echo $PATH | tr ":" "\n"'

4
  • 1
    This answer mentioned it already..
    – heemayl
    Mar 25, 2015 at 12:12
  • Yeah, but I like the suggestion for the alias.
    – 7stud
    Jan 14, 2017 at 5:06
  • A tiny variation makes this work in Git Bash for Windows echo %PATH% | tr ";" "\n"
    – f0nzie
    Jun 6, 2020 at 21:18
  • Just... don't use windows.
    – dolt
    Mar 12, 2021 at 16:03
8

In this answer:

  1. C
  2. Python
  3. Ruby
  4. Alternative awk

1. C

Since all the scripting languages have been taken already, I'll go with C. It's quite easy to get environment variables with get_env() function ( see GNU C Library documentation). The rest is simply character manipulation

bash-4.3$ cat get_path.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    char *path = getenv("PATH");
    int length = strlen(path) -1;
    for(int i=0;i<=length;i++){
        if (path[i] == ':')
            path[i] = '\n';
        printf("%c",path[i]);
    }
    printf("\n");
    return 0;
}
bash-4.3$ gcc get_path.c
bash-4.3$ ./a.out 
/home/xieerqi/bin
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
/snap/bin
/opt/microchip/xc16/v1.25/bin
/opt/microchip/xc32/v1.40/bin
/opt/microchip/xc8/v1.35/bin
/home/xieerqi/bin
/home/xieerqi/bin/sh

2. Python

But also because "why not" , here's alternative python version via command line arguments sys.argv

python -c 'import sys; print "\n".join(sys.argv[1].split(":"))' "$PATH"

3. Ruby

Ruby doesn't come with Ubuntu by default, unlike C compiler and Python interpreter, but if you ever find yourself using it, the solution in Ruby would be this:

ruby -ne 'puts $_.split(":")' <<< "$PATH"

As suggested by 7stud (Thank you very much !) in the comments, this can also be shortened with

ruby -F: -ane 'puts $F' <<<$PATH

and this way

ruby -0072 -ne 'puts chomp' <<<$PATH

4. Alternative awk

We can utilize split() function to break down the line read into array, and use for-each loop to print out each item on separate line.

awk '{split($0,arr,":"); for(var in arr) print arr[var]}' <<< $PATH
6
  • 1
    Nice ruby example. puts automatically prints the elements of an array on separate lines! You can also make the switches do the split: ruby -F: -ane 'puts $F' <<<$PATH Explanation: -F sets $; to the specified character, which is the default separator used by String::split ($; has a default value of nil, which splits on whitespace). -a calls $_.split, where $_ is a line read in using gets()) and assigns the resulting array to $F.
    – 7stud
    Jan 14, 2017 at 4:37
  • @7stud hey, thanks ! :) Didn't know of there's -F flag - I am just starting out with Ruby, so doing things slightly crude way. Jan 14, 2017 at 4:42
  • No, this is obscure stuff. Your one liner is very readable, and clarity should trump brevity every time.
    – 7stud
    Jan 14, 2017 at 4:43
  • Hah. I figured out a shorter one: ruby -0072 -ne 'puts chomp' <<<$PATH. Explanation: -0 sets the input record separator(specify a character in octal format; 072 is a colon). Ruby employs $_ in ways similar to Perl. The gets() method (used in the -n loop) sets $_ to the current line that's read in. And chomp() without an arg, chomps $_. I still like yours better. :)
    – 7stud
    Jan 14, 2017 at 11:26
  • @7stud actually, the one you posted earlier, with -F flag, is shorter. If you do echo "ruby -F: -ane 'puts $F' <<<$PATH" |wc -c that tells me it's 271, bytes, but the one with octal number is 276. That's for the whole command, of course, If we consider only the code itself puts $F is clearly shorter. :) By the way , do you know about Code Golf ? It's the site for solutions to programming puzzles in shortest number of bytes. There's a question related to this one : codegolf.stackexchange.com/q/96334/55572 Jan 14, 2017 at 11:36
7

Here's the equivalent in Go:

$ cat path.go
package main

import (
    "fmt"
    "os"
    "strings"
)

func main() {
    for _, p := range strings.Split(os.Getenv("PATH"), ":") {
        fmt.Println(p)
    }
}

$ go run path.go
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
/snap/bin
/home/nathan/.local/bin
/home/nathan/go/bin
7

Here are a few more approaches. I'm using a PATH with directories containing backslashes, spaces and even a new line to show that they should work with anything (except the cut one which fails on newlines):

$ echo "$PATH"
/bin:usr/bin/:/usr/local/bin:/some\ horrible thing:/even 
new lines
  • Some Perl ways:

    $ perl -pe 's/:/\n/g' <<<"$PATH"
    /bin
    usr/bin/
    /usr/local/bin
    /some\ horrible thing
    /even 
    new lines
    

    The -p means "print every input line after applying the script given by -e". The script is using the substitution operator (s/oldnew/) to replace all : with newlines.

    $ perl -lne 'print for split /:/' <<<"$PATH"
    /bin
    usr/bin/
    /usr/local/bin
    /some\ horrible thing
    /even 
    new lines
    

    The -l adds a newline to each print call. Here, the script is splitting its input on : and then loops over each split element and prints it.

    $ perl -F: -ane '$"="\n";print "@F"' <<<"$PATH"
    /bin
    usr/bin/
    /usr/local/bin
    /some\ horrible thing
    /even 
    new lines
    

    The -a makes perl behave like awk: it will split each of its input lines on the character given by -F (so :, here) and save the result in the array @F. The $" is a special Perl variable, the "list separator", whose value is printed between each element of a printed list. So setting it to a newline will make print @list print each element of @list and then a newline. Here, we're using it to print @F.

    $ perl -F: -ane 'print join "\n", @F' <<<"$PATH"
    /bin
    usr/bin/
    /usr/local/bin
    /some\ horrible thing
    /even 
    new lines
    

    Same idea as above, just less golfed. Instead of using $", we are explicitly joining the array with newlines and then printing.

  • Simple grep with PCRE magic:

    $ grep -oP '(^|:)\K[^:]+' <<<"$PATH"
    /bin
    usr/bin/
    /usr/local/bin
    /some\ horrible thing
    /even 
    new lines
    

    The -o makes grep print only the matching portion of each line, so each match is printed on a separate line. The -P enables Perl Compatible Regular Expressions (PCRE). The regex is looking for stretches of non-: ([^:]+) that follow either the beginning of the line (^) or a : character. The \K is a PCRE trick that means "discard anything matched before this point" and is used here to avoid printing the : as well.

  • And a cut solution (this one fails on newlines, but can deal with backslashes and spaces):

    $ cut -d: -f 1- --output-delimiter=$'\n' <<<"$PATH"
    /bin
    usr/bin/
    /usr/local/bin
    /some\ horrible thing
    /even 
    new lines
    

    The options used are -d: which sets the input delimiter to :, -f 1- which means print all fields (from the 1st to the end) and --output-delimiter=$'\n' which sets the, well, output delimiter. The $'\n' is ANSI C quoting and is a way to print a newline character in the shell.

In all of the above examples, I am using bash's (and some other shells') here string (<<<) operator to pass a string as input to a program. So command <<<"foo" is equivalent to echo -n "foo" | command. Note that I am always quoting "$PATH", without the quotes, the shell would have eaten the newline character.


@7stud gave another approach in the comments that's just too good not to include:

$ perl -0x3a -l012 -pe '' <<<"$PATH"

That's what's known as golfing. The -0 specifies the input record separator as an octal or hexadecimal number. This is what defines a "line" and its default value is \n, a newline character. Here, we're setting it to a : which is x3a in hex (try printf '\x3a\n'). The -l does three things. First it removes the input record separator ($/) from the end of each line—effectively removing the : here—and second, it sets the output record separator ($\) to whatever octal or hex value it is given (012 is \n). If $\ is defined, it is added to the end of each print call, so this will result in a newline appended to each print.

The -pe will print each input line after applying the script given by -e. Here there is no script because all the work is done by the option flags as described above!

7
  • Your perl one liners are not obscure enough! Here’s a version where there’s no code for the -e switch to execute because the other switches handle everything: perl -0x3a -l012 -pe '' <<<$PATH. Explanation: -0 sets the input record separator (specified in hex/octal notation, x3A is a colon), -l does two things: 1) it chomps the input record separator, 2) it sets the output record separator if one is specified (in octal notation, 012 is a newline). A -p loop prints out the value of $_ , which will be each line read in.
    – 7stud
    Jan 14, 2017 at 4:59
  • Also note that your examples using -Fcan eliminate the -a and -n flags, as -F turns those on automatically: perl -F: -e 'print(join "\n", @F);' <<<$PATH. See perlrun. Nice examples.
    – 7stud
    Jan 14, 2017 at 5:00
  • @7stud wow, that's really kind of brilliant, thanks! Shamelessly stolen and added to my answer (I assume you don't mind, feel free to post it as an answer yourself and I'll delete it). And thanks for the -F info. I've been using -F combined with -an for years, I never realized the one implied the others. By the way, I saw Serg mentioned Code Golf, but I think you'd also enjoy Unix & Linux if you're into this sort of thing.
    – terdon
    Jan 14, 2017 at 12:46
  • Hey, thanks. One clarification about this statement: Finally, -l also adds the output record separator given to the end of each print call. In fact, print always adds the output record separator, $/, to the end of a string--if the output record separator is defined. By default it is is undef. So -l jus sets $/, and that causes print to add the newline to the end of the string.
    – 7stud
    Jan 14, 2017 at 13:26
  • I assume you don't mind -- Not at all. :)
    – 7stud
    Jan 14, 2017 at 13:31
5

We need more Java!

public class GetPathByLine {
    public static void main(String[] args) {
        for (String p : System.getenv("PATH").split(":")) {
            System.out.println(p);
        }
    }
}

Save this to GetPathByLine.java, and compile using:

javac GetPathByLine.java

Run with:

java GetPathByLine

┌─[17:06:55]─[kazwolfe@BlackHawk]
└──> ~ $ cat GetPathByLine.java 
public class GetPathByLine {
    public static void main(String[] args) {
        for (String p : System.getenv("PATH").split(":")) {
            System.out.println(p);
        }
    }
}
┌─[17:06:58]─[kazwolfe@BlackHawk]
└──> ~ $ javac GetPathByLine.java 
┌─[17:07:02]─[kazwolfe@BlackHawk]
└──> ~ $ java GetPathByLine 
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
/snap/bin
2
  • Wow this is overkill. You can do this in one line in Python 3: python3 -c "import os; [print(p) for p in os.getenv('PATH').split(':')]"
    – wjandrea
    Nov 7, 2018 at 5:11
  • 1
    @wjandrea That's the point! :D
    – Kaz Wolfe
    Nov 7, 2018 at 19:59
4

Through awk.

echo $PATH | awk -F: '{for(i=1;i<=NF;i++)print $i}'

Through python.

$ echo $PATH | python3 -c 'import fileinput
for line in fileinput.input():
    for i in line.split(":"):
        print(i)'

Note that Indentation is very important in python.

2
  • Shorter awk: echo $PATH | awk '{gsub(/\:/,"\n");print}' Oct 14, 2016 at 23:10
  • I'mm not sure why you bother with fileinput when you could just use input: python3 -c 'print(*input().split(":"), sep="\n")' <<< "$PATH"
    – wjandrea
    Nov 7, 2018 at 5:16
2

I use Stephen Collyer's "Bash Path Functions" (see his article in Linux Journal). It permits me to use the "colon separated list" as a datatype in shell programming. For example, I can produce a list of all the directories in the current directory by:

dirs="";for i in * ; do if [ -d $i ] ; then addpath -p dirs $i; fi; done  

Then, listpath -p dirs produces a list.

2

Explanation for @Cyrus answer

echo "${PATH//:/$'\n'}"

Notes:

ANSI-C Quoting - it explains $'some\ntext'

Shell Parameter Expansion - it explains ${parameter/pattern/string}, If pattern begins with ‘/’, all matches of pattern are replaced with string.

So we have:

  1. a pattern /: which start with '/' to replace all matches
  2. a string $'\n' which is quoted with $'anytext' contraction to treat new line symbol (\n).
1

Another AWK way is to treat each directory as a separate record, rather than as a separate field.

awk 'BEGIN{RS=":"} {print $0}' <<<"$PATH"

I find that syntax particularly intuitive. But, if you like, you can shorten it by making the print $0 implicit (it is the default action, and 1 evaluates to true, causing it to be done for every line):

awk 'BEGIN{RS=":"} 1' <<<"$PATH"

AWK's default input and output record separator is the newline (line break). By setting the input record separator (RS) to : before reading the input, AWK automatically parses a colon-deliminated $PATH into its constitutent directory names. AWK expands $0 to each whole record, the newline remains the output record separator, and no looping or gsub is needed.

ek@Io:~$ echo "$PATH"
/home/ek/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
ek@Io:~$ awk 'BEGIN{RS=":"} {print $0}' <<<"$PATH"
/home/ek/bin
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
/snap/bin

AWK is often used to parse records into separate fields, but there's no need for that just to construct a list of directory names.

This works even for input containing blanks (spaces and tabs), even multiple consecutive blanks:

ek@Io:~$ awk 'BEGIN{RS=":"} {print $0}' <<<$'ab\t\t c:de    fg:h'
ab               c
de    fg
h

That is, unless you cause AWK to rebuild the record (see below), it's no problem to have spaces or tabs (the default field separators) in the input. Your PATH probably doesn't contain spaces on an Ubuntu system, but if it does, this will still work.


It's worth mentioning, as a side note, that AWK's ability to interpret a record as a collection of fields becomes useful for the related problem of constructing a table of directory components:

ek@Io:~$ awk -F/ 'BEGIN{RS=":"; OFS="\t"} {$1=$1; print $0}' <<<"$PATH"
        home    ek      bin
        usr     local   sbin
        usr     local   bin
        usr     sbin
        usr     bin
        sbin
        bin
        usr     games
        usr     local   games
        snap    bin

The curious $1=$1 assignment serves the purpose of forcing AWK to rebuild the record.

(This is probably more useful for cases where additional processing is to be done on the components, than for the exact example shown of simply printing the table.)

1
jq -Rr 'gsub(":";"\n")' <<<$PATH
1

How to display the paths in $PATH separately

These are my preferred ways of doing this based on my respective use-cases and concerns about compatibility and resource usage.

tr

First, if you need a quick, easy to remember, and readable solution, just echo PATH and pipe it to translate (tr) to turn the colons into newlines:

echo $PATH | tr : "\n"

It has the downside of using two processes because of the pipe, but if we're just hacking in a terminal, do we really care about that?

Bash's shell parameter expansion

If you want a fairly permanent solution in your .bashrc for interactive use, you can alias the following command to path, but the readability of this solution is questionable:

alias path="echo \"${PATH//:/$'\n'}\""

If pattern begins with ‘/’, all matches of pattern are replaced with string. Normally only the first match is replaced.

The above command substitutes the colon with newlines using Bash's Shell Parameter Expansion:

${parameter/pattern/string}

To explain it:

          # v--v---------delimiters, a'la sed
echo "${PATH//:/$'\n'}"
          #  ^^ ^^^^^----string, $ required to get newline character.
          #   \----------pattern, / required to substitute for *every* :.

Good luck remembering this when you're just hacking on the command line, if you haven't aliased it, though.

IFS, tested in Bash and Dash

Alternatively, a fairly cross-compatible, readable, and understandable approach that doesn't rely on anything other than the shell is use the following function (I suggest in your .bashrc.)

The following function temporarily makes the Internal (or Input) Field Separator (IFS) a colon, and when an array is given to printf, it executes until the array is used up:

path () {
    local IFS=:
    printf "%s\n" ${PATH}
    }

This method of function creation, IFS, and printf are provided for by posix, so it should work in most posix-like shells (especially Dash, which Ubuntu usually aliases as sh).

Python

Should you use Python for this? You could. This is the shortest Python command I can think of for this:

python -c "print('\n'.join('${PATH}'.split(':')))"

or Python 3 only (and perhaps more readable?):

python3 -c "print(*'${PATH}'.split(':'), sep='\n')"

These should work in any usual shell environment as well, so long as you have Python.

2
  • Is it possible you need to \$ escape the first $ dollar sign too? Otherwise I think this does the reformatting only once at load-time, instead of every time you run the path command.
    – Bob Stein
    Nov 9, 2022 at 20:27
  • 1
    alias path="echo \"\${PATH//:/$'\n'}\"" was the command I needed to use.
    – Bob Stein
    Nov 9, 2022 at 20:29
1

This solution is simpler than the Java, C, go and awk solutions:

$ LPATH=$PATH wine cmd /c echo %LPATH::=$'\n'% 2> /dev/null
/usr/local/bin
/usr/local/sbin
/usr/bin
/usr/sbin

Here's another great possibility:

$ jrunscript -classpath /usr/share/java/bsh.jar -e 'print(java.lang.System.getenv("PATH").replaceAll(":","\n"))'
/usr/local/bin
/usr/local/sbin
/usr/bin
/usr/sbin

This would require some dependencies to be installed:

sudo apt-get install openjdk-8-jdk-headless bsh
0

This works in Git Bash for Windows:

python -c "import os; print os.environ['PATH'].replace(';', '\n')"

It breaks the lines nicely:

c:\Rtools\mingw_32\bin
C:\Program Files (x86)\Intel\MPI-RT\4.1.3.045\ia32\bin
C:\Windows\system32
C:\Windows
C:\Windows\System32\Wbem
C:\Windows\System32\WindowsPowerShell\v1.0\
C:\ProgramData\chocolatey\bin
C:\Program Files (x86)\vim\vim74

You must log in to answer this question.

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