如何在Bash中修剪字符串变量

3

我有一些最初包含不同目录路径的字符串,其中第二个和倒数第二个子目录的长度都可能不同,就像这样

 /home/Leo/Work/CMI/ARCH/MWS/Disks
 /home/Cleo/Work/CMI/ARCH/BK/Disks

我想裁剪前5个子目录,只显示最后2个,如下所示。
 echo "/MWS/Disks"
 echo "/BK/Disks"

有一种方法可以从初始字符串中删除前5个子目录,即将每个字符左移,直到两个字符串以倒数第二个“/”开头。

Bash初学者指南描述了一个内置的shift命令,它左移命令中的位置参数并丢弃未使用的参数。但是不清楚是否可以使用它来从上述字符串中删除前5个子目录。

在Bash中,如何缩短这些字符串,最好不使用循环?


澄清

从评论中可以看出需要更多的上下文。我的Bash脚本从8英寸软盘镜像中恢复历史Mdos和Qdos文件,并将文件保存到硬盘上的目录中。

不管好坏,我创建了一个定制方案,使用3个字符的变量名存储目录路径,其中每个名称都是当前目录路径部分的首字母缩写。

例如,在以下路径中,MWC$MY/Work/CMI 的首字母缩写。

MY="$USER"
MWC="C:/cygwin64/home/$MY/Work/CMI"
cd "$MWC"
pwd
C:/cygwin64/home/$MY/Work/CMI

同样,三个字符的变量指向树形结构中更高层次的下一个子目录。
WCA="$MWC/ARCH"

即: C:/cygwin64/home/$MY/Work/CMI/ARCH,是一个存档所有者图库的路径。
随着目录路径的加长,3个字符的变量可通过在列表中保留空格来轻松识别路径。然而,每当我的脚本引用路径时,完整路径都会出现。因此需要裁剪对最终用户无用的字符串部分。

你有使用sed的可能性吗?echo /home/Leo/Work/CMI/ARCH/MWS/Disks | sed 's#^\(/[^/]*\)\{5\}##' - David Lukas
是的,尽管这个例子没有成功。 - Greg
我在在线网站上尝试了一下。 - David Lukas
你是如何处理这个目录/文件列表的?它们来自一个文件吗?从单独的操作系统进程流中获取?还是通过变量逐一获取(例如,在循环中)? - markp-fuso
4个回答

8

如果子目录的数量始终相同,您可以使用参数扩展来删除前5个子目录:

s=/home/Leo/Work/CMI/ARCH/MWS/Disks
s=/${s#/*/*/*/*/*/}
echo $s  # /MWS/Disks

或者,如果您知道无论路径深度如何,都需要最后两部分:

s=/home/Leo/Work/CMI/ARCH/MWS/Disks
last=/${s##*/}
last_but1=${s%$last}
last_but1=/${last_but1##*/}
echo $last_but1$last  # /MWS/Disks
  • ${s#PATTERN}$s的开头中移除PATTERN
  • ${s%PATTERN}$s的末尾中移除PATTERN
  • 使用#%,可以找到最短匹配的PATTERN。将它们重复,则可以找到最长匹配。

最后两个部分是必要的,这样用户(在第二个子目录中命名)就知道他们正在处理谁的磁盘归档(即在倒数第二个子目录中命名的归档所有者)- 如果这有意义的话。 - Greg
@Greg:我重新表述了注释,以解释这两种方法之间的区别。它们都可能适用于你。 - choroba
这很好。我正在检查每一个。你有关于参数扩展的参考资料吗? - Greg
man bash 的描述有点简洁,因此需要通过实验来理解其中的细微差别 :-) - choroba
1
你说得没错。需要一些实验才能看出这个答案是如何回答问题的。如果只是阅读bash手册,我绝对不可能想到这个答案。三个要点真的很有帮助。 - Greg
显示剩余2条评论

2
作为参数扩展的替代方案,您可以使用=~运算符:
dir='/home/Leo/Work/CMI/ARCH/MWS/Disks'
[[ $dir =~ /[^/]*/[^/]*$ ]] && echo "${BASH_REMATCH[0]}"

我不确定你改了什么,但在编辑之前和之后都能正常工作。你可以推荐一个关于 =~ 运算符的参考资料吗? - Greg
@Greg Bash参考手册,条件构造。在页面中搜索=〜 - M. Nejat Aydin
@Greg 同时,正则表达式 - M. Nejat Aydin

0

假设输入来自文件(或从另一个操作系统进程流式传输/管道传输)...

示例输入:

$ cat dir.file
 /home/Leo/Work/CMI/ARCH/MWS/Disks
 /home/Cleo/Work/CMI/ARCH/BK/Disks

一个关于awk的想法:
awk 'BEGIN {FS=OFS="/"} {print OFS $(NF-1),$(NF)}' dir.file

这将生成:

/MWS/Disks
/BK/Disks

如果需要将结果存储以供以后使用,只需根据需要添加一些代码即可(例如,重定向到文件、管道到另一个进程、作为输入传递给while/read循环、加载到数组中等)。
如果 OP 一次处理一个字符串(例如,在循环中作为变量),我可能会坚持使用参数替换解决方案(请参见 choroba 的答案),这不需要产生任何开销来生成子进程。

看看我的修改,它们回答了你的问题。你的 awk 解决方案可行,但我会在我的脚本中尝试后再确认。 - Greg

0

Cygwin 有 find 命令,对吧?

那么你可以这样做:

cd 'C:/cygwin64/' # I guess?

find "/home/$USER/work/CMI/ARCH" -mindepth 2 -maxdepth 2 -type d -name Disks |
sed 's=/Disks$=='

或者这样,列出所有的:

find /home/*/work/CMI/ARCH/*/ -type d -name Disks |
sed 's=/Disks$=='

如果您只想编辑字符串,可以使用前缀删除:
$ path=/home/Leo/Work/CMI/ARCH/MWS/Disks
$ path=/${path#/*/*/*/*/*/*}
$ echo "$path"
/MWS/Disks

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