在Unix系统中删除路径的一部分

91

我想要删除字符串中某部分路径。我有这个路径:

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

我想要移除第一部分/path/to/file/drive并输出:

file/path/

注意:我在一个while循环中有几个路径,所有这些路径都包含相同的/path/to/file/drive, 但我只是想知道如何删除所需的字符串。

我找到了一些示例,但我无法让它们工作:

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 是字符串的第二部分,我明显做错了什么...也许有更简单的方法?

8个回答

129
如果你想要删除一定数量的路径组件,你应该使用带有-d'/'选项的cut命令。例如,如果path=/home/dude/some/deepish/dir,要删除前两个组件:
# (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

保留前两个组件:

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

要删除最后两个组件(rev将字符串反转):

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

保留最后三个组件:

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

或者,如果你想删除特定组件之前的所有内容,可以使用 sed

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

或者在特定组件之后:

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

如果您不想保留正在指定的组件,那么这将变得更加容易:

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

如果你想要保持一致性,你也可以匹配末尾的斜杠:

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

当然,如果你要匹配多个斜杠,你应该更改 sed 分隔符:

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

请注意,这些示例都使用绝对路径,您需要尝试一下才能让它们使用相对路径。


非常棒的答案,特别是双重 rev 技巧。明天我的投票限制解除后会点赞。 - Ciro Santilli OurBigBook.com
1
在 Unix 的精神中,这是一篇优秀、信息丰富的答案,比 {chopsquiggleSquiggle}:only:shellversionyoumaynothave 好得多。 - narration_sd
@jojoob 那个拒绝对我来说也没有任何意义。有时候SO的管理和大佬们很疯狂。我会看看能不能解决这个问题。可能是你的编辑摘要引起了他们的反感。 - ACK_stoverflow
1
这太棒了。如果删除特定组件之前的所有内容但不保留该组件(我不知道下一个组件是什么),那怎么样呢?即保留指定组件之后的所有内容? - Gamora
1
@Gamora 很高兴这对你有帮助 :) 要做到这一点很简单,只需不捕获路径组件即可。所以:echo $path | sed 's/.*some//g' 老实说,再次查看这个多年后 - 今天我会确保“some”不会匹配其他目录子字符串 - 所以类似于 sed 's!.*/some/!!g' 的东西(如果“some”可能是路径的基本名称,则可能无法正常工作)。 - ACK_stoverflow
显示剩余6条评论

91

你还可以使用 POSIX shell 变量扩展来实现这一点。

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

#.. 部分在变量扩展时会去掉前导匹配的字符串;如果你的字符串已经在shell变量中,像使用 for 循环一样,这非常有用。您还可以使用%...从变量末尾删除匹配字符串(例如扩展名)。请参阅 bash 的手册以获取详细信息。


谢谢...是的,我正在使用shell变量来执行此操作。两个部分都是变量,一个是我需要删除的部分,另一个是完整路径。 - esausilva
太好了!但是当字符串来自“管道”时,我该如何使用它? - birgersp
@Birger 如果字符串来自管道,则需要将其放入变量中以使用shell变量扩展。或者在这种情况下只需使用 sed - evil otto
https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html - Roland
1
@birgersp:那么,不要使用 command | …,而是使用 myvar="$( command )"; echo ${path#$myvar/}。它可以按预期工作。(请注意末尾的 /)@Birger:如果要删除的路径使用了正则表达式/sed语法中的字符,则sed将失败。而且转义是一场噩梦,总是比你想象的更难一点。^^ - anon

33

如果您不想硬编码要删除的部分:

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

2
我花了2个小时查找如何实现这个。而你是唯一一个发布了一种在不硬编码的情况下仅显示目录名称的方法的人。我需要执行以下操作:'find /path/to/file/drive/file/*_TST -maxdepth 0 -type d',但我只想显示目录的名称而不是完整路径。这就完成了。 - Whitecat
@Whitecat 如果这是你想要的,那么请查看"man find",特别是"-printf"。你可以指定'-printf "%h\n"'。请记住,它会为每个匹配的文件打印目录,因此你需要通过"uniq"进行过滤。 - Capt. Crunch

12

使用sed实现该功能的一种方式是:

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

1
太棒了 - 我不知道sed分隔符可以是“:”(冒号)而不是“/”(斜杠)。优雅并且完美地工作。 - stachyra
2
你可以使用几乎任何分隔符来操作sed,不仅限于/和:,只要你保持一致即可。 - Prisoner 13
@Prisoner13 每天都有新的东西! - coffman21
每当路径中包含正则表达式/ sed 语法时,这也会严重破坏并可能默默无声地失败。假设路径不是硬编码的,而它本来就不应该是,这是Bobby Tables的情况。 ;) - anon

7
如果您想要删除路径的前N部分,您可以使用dirname N次调用,例如Glenn的回答中所示,但是使用通配符可能更加简单:
path=/path/to/file/drive/file/path/
echo "${path#*/*/*/*/*/}"   #  file/path/

具体而言,${path#*/*/*/*/*/}的意思是"返回除包含5个斜杆的最短前缀之外的"


3

使用evil otto建议的${path#/path/to/file/drive/}肯定是实现这个目标的典型和最佳方式,但由于有许多sed建议,值得指出的是,如果您正在处理一个固定字符串,则使用sed会过度杀伤。 您也可以这样做:

echo $PATH | cut -b 21-

要丢弃前20个字符。类似地,在bash中可以使用${PATH:20},在zsh中可以使用$PATH [20,-1]

谢谢。我正在使用evil otto的答案。我想要删除的部分是固定的,但如果我将脚本移动到不同的框/不同的路径,则可能会更改。因此,将我想要从路径中删除的部分放在变量中比计数更容易...但你提供的信息很好知道 :) - esausilva

1

纯bash,不硬编码答案

basenames()
{
  local d="${2}"
  for ((x=0; x<"${1}"; x++)); do
    d="${d%/*}"
  done
  echo "${2#"${d}"/}"
}
  • 参数1 - 您要保留多少级别(原问题中为2)
  • 参数2 - 完整路径

来自vsi_common原始版本


1
哇,这个页面上最佳答案!我需要通用情况下最可移植的结构。你做到了!谢谢! - James Madison

1

这里有一个使用简单的bash语法的解决方案,可以适应变量(以防您不想硬编码完整路径),消除了将stdin导入到sed的需要,并包括一个for循环,以备不时之需:

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

如 @evil otto 上面提到的,这种情况下 # 符号用于删除前缀。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接