esausilva
esausilva

Reputation: 2116

Remove part of path on Unix

I'm trying to remove part of the path in a string. I have the path:

/path/to/file/drive/file/path/

I want to remove the first part /path/to/file/drive and produce the output:

file/path/

Note: I have several paths in a while loop, with the same /path/to/file/drive in all of them, but I'm just looking for the 'how to' on removing the desired string.

I found some examples, but I can't get them to work:

echo /path/to/file/drive/file/path/ | sed 's:/path/to/file/drive:\2:'
echo /path/to/file/drive/file/path/ | sed 's:/path/to/file/drive:2'

\2 being the second part of the string and I'm clearly doing something wrong...maybe there is an easier way?

Upvotes: 100

Views: 138267

Answers (9)

Aki K
Aki K

Reputation: 1262

I was looking to do something similar, but instead of removing the parts of the path, I wanted to truncate them, similar to how it's done in the Starship prompt which is inspired by the fish shell.

So, paths can look like so:

Original Path Truncated Path
/home/user/adir/bdir/cdir/ddir ~/a/b/c/ddir
/home/user/adir/.bdir/cdir/ddir ~/a/.b/c/ddir
/Users/user/adir/bdir/cdir/ddir ~/a/b/c/ddir
/home /home
/Users /Users
/etc/var/adir/bdir/cdir/ddir /e/v/a/b/c/ddir
/etc /etc

To achieve that, you can write a bash function like so:

functions.sh

#!/usr/bin/env bash

function truncate_path() {
  local path_to_truncate truncation_length truncated_path

  if [ -z "$1" ] || [ -z "$2" ]; then
    echo "Usage: truncate_path <path> <truncation_length>"
    return 1
  fi

  path_to_truncate="$1"
  truncation_length="$2"

  if ! [[ "$truncation_length" =~ ^[0-9]+$ ]]; then
    echo "Error: truncation_length must be a positive integer"
    return 1
  fi

  truncated_path=$(
    echo "$path_to_truncate" |
      sed "s|^$HOME|~|g" |
      awk -F "/" -v OFS="/" -v truncation_length="$truncation_length" '{
        if (NF <= truncation_length) {
          print;
          exit;
        }

        for (i = 2; i <= NF-truncation_length; i++) {
          if (substr($i, 1, 1) == ".") {
            $i = substr($i, 1, 2);
          } else {
            $i = substr($i, 1, 1);
          }
        }
        print;
      }'
  )

  echo "$truncated_path"
}

Source it in your .zshrc (or .bashrc):

~/.zshrc

source ~/scripts/functions.sh

And then call it:

$ truncate_path "/home/user/adir/bdir/cdir/ddir" 1
~/a/b/c/ddir

Upvotes: 0

Fritz
Fritz

Reputation: 1430

If you want to remove the first N parts of the path, you could of course use N calls to dirname, as in glenn's answer, but it's probably easier to use globbing:

path=/path/to/file/drive/file/path/
echo "${path#*/*/*/*/*/}"   #  file/path/

Specifically, ${path#*/*/*/*/*/} means "return $path minus the shortest prefix that contains 5 slashes".

Upvotes: 7

ACK_stoverflow
ACK_stoverflow

Reputation: 3305

If you wanted to remove a certain NUMBER of path components, you should use cut with -d'/'. For example, if path=/home/dude/some/deepish/dir:

To remove the first two components:

# (Add 2 to the number of components to remove to get the value to pass to -f)
echo $path | cut -d'/' -f4-
# output:
# some/deepish/dir

To keep the first two components:

echo $path | cut -d'/' -f-3
# output:
# /home/dude

To remove the last two components (rev reverses the string):

echo $path | rev | cut -d'/' -f4- | rev
# output:
# /home/dude/some

To keep the last three components:

echo $path | rev | cut -d'/' -f-3 | rev
# output:
# some/deepish/dir

Or, if you want to remove everything before a particular component, sed would work:

echo $path | sed 's/.*\(some\)/\1/g'
# output:
# some/deepish/dir

Or after a particular component:

echo $path | sed 's/\(dude\).*/\1/g'
# output:
# /home/dude

It's even easier if you don't want to keep the component you're specifying:

echo $path | sed 's/some.*//g'
# output:
# /home/dude/

And if you want to be consistent you can match the trailing slash too:

echo $path | sed 's/\/some.*//g'
# output:
# /home/dude

Of course, if you're matching several slashes, you should switch the sed delimiter:

echo $path | sed 's!/some.*!!g'
# output:
# /home/dude

Note that these examples all use absolute paths, you'll have to play around to make them work with relative paths.

Upvotes: 140

itsmisterbrown
itsmisterbrown

Reputation: 531

Here's a solution using simple bash syntax that accommodates variables (in case you don't want to hard code full paths), removes the need for piping stdin to sed, and includes a for loop, for good measure:

FULLPATH="/path/to/file/drive/file/path/"
SUBPATH="/path/to/file/drive/"
for i in $FULLPATH;
do
echo ${i#$SUBPATH}
done

as mentioned above by @evil otto, the # symbol is used to remove a prefix in this scenario.

Upvotes: 1

Andy
Andy

Reputation: 3215

Pure bash, without hard coding the answer

basenames()
{
  local d="${2}"
  for ((x=0; x<"${1}"; x++)); do
    d="${d%/*}"
  done
  echo "${2#"${d}"/}"
}
  • Argument 1 - How many levels do you want to keep (2 in the original question)
  • Argument 2 - The full path

Taken from vsi_common(original version)

Upvotes: 1

William Pursell
William Pursell

Reputation: 212684

Using ${path#/path/to/file/drive/} as suggested by evil otto is certainly the typical/best way to do this, but since there are many sed suggestions it is worth pointing out that sed is overkill if you are working with a fixed string. You can also do:

echo $PATH | cut -b 21-

To discard the first 20 characters. Similarly, you can use ${PATH:20} in bash or $PATH[20,-1] in zsh.

Upvotes: 3

glenn jackman
glenn jackman

Reputation: 247250

If you don't want to hardcode the part you're removing:

$ s='/path/to/file/drive/file/path/'
$ echo ${s#$(dirname "$(dirname "$s")")/}
file/path/

Upvotes: 33

evil otto
evil otto

Reputation: 10582

You can also use POSIX shell variable expansion to do this.

path=/path/to/file/drive/file/path/
echo ${path#/path/to/file/drive/}

The #.. part strips off a leading matching string when the variable is expanded; this is especially useful if your strings are already in shell variables, like if you're using a for loop. You can strip matching strings (e.g., an extension) from the end of a variable also, using %.... See the bash man page for the gory details.

Upvotes: 96

Jonathan Callen
Jonathan Callen

Reputation: 11621

One way to do this with sed is

echo /path/to/file/drive/file/path/ | sed 's:^/path/to/file/drive/::'

Upvotes: 12

Related Questions