bash script regex to get directory path up nth levels
I'm using PWD to g开发者_运维技巧et the present working directory. Is there a SED or regex that I can use to, say, get the full path two parents up?
Why sed or regex? Why not dirname
:
parent=`dirname $PWD`
grandparent=`dirname $parent`
Edit:
@Daentyh points out in the comments that apparently $()
is preferred over backquotes ``
for command substitution in POSIX-compliant shells. I don't have experience with them. More info:
http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_03
So, if this applies to your shell, you can (should?) use:
parent=$(dirname $PWD)
grandparent=$(dirname $parent)
This should work in POSIX shells:
echo ${PWD%/*/*}
which will give you an absolute path rather than a relative one.
Also, see my answer here where I give two functions:
cdn () { pushd .; for ((i=1; i<=$1; i++)); do cd ..; done; pwd; }
which goes up n levels given n as an argument.
And:
cdu () { cd "${PWD%/$1/*}/$1"; }
which goes up to a named subdirectory above the current working directory.
why not use
"${PWD}/../.."
?
Not sed or regex, but this does do arbitrary parent quantity:
$ cd $(mktemp -dp $(mktemp -dp $(mktemp -dp $(mktemp -d)))) && \
> n=3 && \
> readlink -f ${PWD}/$(for i in $(seq ${n}); do echo -n '../' ; done)
/tmp/tmp.epGcUeLV9q
In this example, I cd
into a 5-deep temporary directory, assign n=3
, construct a relative path n
levels up from ${PWD}
, and -f, --canonicalize
the result with readlink
.
Here's the regex that works for me. It's a little different between grep and Perl/sed:
The extended regex breaks up paths into 0 or more groups of /ABC123
anchored to the end of line, essentially working backward. (.*)
consumes everything prior to this match and replaces it.
user@home:~/adir/bdir$ pwd
/home/user/adir/bdir
user@home:~/adir/bdir$ pwd | perl -pe 's|(.*)((/.*?){0})$|\1|'
/home/user/adir/bdir
user@home:~/adir/bdir$ pwd | perl -pe 's|(.*)((/.*?){1})$|\1|'
/home/user/adir
user@home:~/adir/bdir$ pwd | sed -r 's|(.*)((/.*?){2})$|\1|'
/home/user
user@home:~/adir/bdir$ pwd | sed -r 's|(.*)((/.*?){3})$|\1|'
/home
user@home:~/adir/bdir$ pwd | sed -r 's|(.*)((/.*?){4})$|\1|'
Grep can simulate substitution using a positive look ahead (?=
which tells grep to match everything except the pattern. -Po
tells grep to use Perl regex and show only the match.
user@home:~/adir/bdir$ pwd
/home/user/adir/bdir
user@home:~/adir/bdir$ pwd | grep -Po '(.*)(?=((/.*?){0})$)'
/home/user/adir/bdir
user@home:~/adir/bdir$ pwd | grep -Po '(.*)(?=((/.*?){1})$)'
/home/user/adir
user@home:~/adir/bdir$ pwd | grep -Po '(.*)(?=((/.*?){2})$)'
/home/user
user@home:~/adir/bdir$ pwd | grep -Po '(.*)(?=((/.*?){3})$)'
/home
user@home:~/adir/bdir$ pwd | grep -Po '(.*)(?=((/.*?){4})$)'
Of course it works equally well for Windows style paths:
C:\home\user\adir\bdir> cd
C:\home\user\adir\bdir
C:\home\user\adir\bdir> cd | perl -pe 's|(.*)((\\.*?){0})$|\1|'
C:\home\user\adir\bdir
C:\home\user\adir\bdir> cd | sed -r 's|(.*)((\\.*?){1})$|\1|'
C:\home\user\adir
C:\home\user\adir\bdir> cd | grep -Po '(.*)(?=((\\.*?){2})$)'
C:\home\user
C:\home\user\adir\bdir> cd | grep -Po '(.*)(?=((\\.*?){3})$)'
C:\home
C:\home\user\adir\bdir> cd | grep -Po '(.*)(?=((\\.*?){4})$)'
C:
Sorry for the edits but I've been working on this enigma for about 16 hours now. Just kept trying different permutations and re-reading the regex docs. It had to sink in eventually.
精彩评论