Reputation: 2116
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
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
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
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
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
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}"/}"
}
Taken from vsi_common(original version)
Upvotes: 1
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
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
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
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