Regular expressions were introduced in bash 3.0. Bash 3.2 changed regular expression handling slightly in that quotes around regular expressions became part of the regular expression. So, if you have a version of bash (with regular expression support) and the code doesn't work, put the regular expression in the while loop in quotes.
The entire function and some test code follows:
#!/bin/bash
#
# Usage: normalize_path PATH
#
# Remove /./ and dir/.. sequences from a pathname and write result to stdout.
function normalize_path()
{
# Remove all /./ sequences.
# 这个替代的表达式不能工作的很好
# 比如 /./ . /./ . /. 这样的形式 只能变成 / . / ./.
local path=${1./}
# Remove dir/.. sequences.
# =~ 是bash中用于正则表达式匹配的 运算符
while [[ $path =~ ([^/][^/]*//././) ]]
do
# BASH_REMATCH[0] 表示匹配上的字符串
path=${path/${BASH_REMATCH[0]}/}
done
echo $path
}
# $0 类似c语言的argv[0]
# $* 表示 所有的参数,不包括arg[0]
# $@ 表示 所有的参数
# 但是我们经过测试,我发现 $* 是一个全局的概念 也就是将所有的参数算成了一个整体
# 所以在for循环中 使用的是 $@ 而不是 $*
# $# 表示 参数个数
if [[ $(basename $0 .sh) == 'normalize_path' ]]; then
if [[ "$*" ]]; then
for p in "$@"
do
printf "%-30s => %s/n" $p $(normalize_path $p)
done
else
for p in /test/../test/file test/../test/file .././test/../test/file
do
printf "%-30s => %s/n" $p $(normalize_path $p)
done
fi
fi
#####################################################################
# vim: tabstop=4: shiftwidth=4: noexpandtab:
# kate: tab-width 4; indent-width 4; replace-tabs false;
Since, older versions of bash don't support regular expressions the second version does the same thing using sed instead:
#!/bin/bash
#
# Usage: normalize_path PATH
#
# Remove /./ and dir/.. sequences from a pathname and write result to stdout.
function normalize_path()
{
# Remove all /./ sequences.
local path=${1./}
# Remove first dir/.. sequence.
local npath=$(echo $path | sed -e 's;[^/][^/]*//././;;')
# Remove remaining dir/.. sequence.
while [[ $npath != $path ]]
do
path=$npath
npath=$(echo $path | sed -e 's;[^/][^/]*//././;;')
done
echo $path
}
if [[ $(basename $(basename $0 .sh) .old) == 'normalize_path' ]]; then
if [[ "$*" ]]; then
for p in "$@"
do
printf "%-30s => %s/n" $p $(normalize_path $p)
done
else
for p in /test/../test/file test/../test/file .././test/../test/file
do
printf "%-30s => %s/n" $p $(normalize_path $p)
done
fi
fi
#####################################################################
# vim: tabstop=4: shiftwidth=4: noexpandtab:
# kate: tab-width 4; indent-width 4; replace-tabs false;
You can run the script directly and it runs a few tests:
$ bash normalize_path.sh
/test/../test/file => /test/file
test/../test/file => test/file
.././test/../test/file => ../test/file
You can also pass in test cases on the command line:
$ bash normalize_path.sh ../d1/./d2/../f1 a/b/c/../d/../e
../d1/./d2/../f1 => ../d1/f1
a/b/c/../d/../e => a/b/e
Normalized path names are never necessary but they're often easier to comprehend at a glance.