Bash变量替换vs dirname和basename

kob*_*ame 21 bash

下一个脚本

str=/aaa/bbb/ccc.txt
echo "str: $str"
echo ${str##*/} == $(basename $str)
echo ${str%/*} == $(dirname $str)
Run Code Online (Sandbox Code Playgroud)

生产:

str: /aaa/bbb/ccc.txt
ccc.txt == ccc.txt
/aaa/bbb == /aaa/bbb
Run Code Online (Sandbox Code Playgroud)

问题是:

  • 在bash脚本中,何时建议使用命令dirnamebasename变量替换时为什么?

主要是因为:

str="/aaa/bbb/ccc.txt"
count=10000

s_cmdbase() {
let i=0
while(( i++ < $count ))
do
    a=$(basename $str)
done
}

s_varbase() {
let i=0
while(( i++ < $count ))
do
    a=${str##*/}
done
}

s_cmddir() {
let i=0
while(( i++ < $count ))
do
    a=$(dirname $str)
done
}

s_vardir() {
let i=0
while(( i++ < $count ))
do
    a=${str%/*}
done
}

time s_cmdbase
echo command basename
echo ===================================
time s_varbase
echo varsub basename
echo ===================================
time s_cmddir
echo command dirname
echo ===================================
time s_vardir
echo varsub dirname
Run Code Online (Sandbox Code Playgroud)

在我的系统上产生:

real    0m33.455s
user    0m10.194s
sys     0m18.106s
command basename
===================================

real    0m0.246s
user    0m0.237s
sys     0m0.007s
varsub basename
===================================

real    0m30.562s
user    0m10.115s
sys     0m17.764s
command dirname
===================================

real    0m0.237s
user    0m0.226s
sys     0m0.007s
varsub dirname
Run Code Online (Sandbox Code Playgroud)

调用外部程序(分叉)需要花费时间.问题的要点是:

  • 使用变量替换而不是外部命令是否存在一些陷阱?

jm6*_*666 21

外部命令进行一些逻辑更正.检查下一个脚本的结果:

doit() {
    str=$1
    echo -e "string   $str"
    cmd=basename
    [[ "${str##*/}" == "$($cmd $str)" ]] && echo "$cmd same: ${str##*/}" || echo -e "$cmd different \${str##*/}\t>${str##*/}<\tvs command:\t>$($cmd $str)<"
    cmd=dirname
    [[ "${str%/*}"  == "$($cmd $str)" ]] && echo "$cmd  same: ${str%/*}" || echo -e "$cmd  different \${str%/*}\t>${str%/*}<\tvs command:\t>$($cmd $str)<"
    echo
}

doit /aaa/bbb/
doit /
doit /aaa
doit aaa
doit aaa/
doit aaa/xxx
Run Code Online (Sandbox Code Playgroud)

结果

string   /aaa/bbb/
basename different ${str##*/}   ><          vs command: >bbb<
dirname  different ${str%/*}    >/aaa/bbb<  vs command: >/aaa<

string   /
basename different ${str##*/}   ><  vs command: >/<
dirname  different ${str%/*}    ><  vs command: >/<

string   /aaa
basename same: aaa
dirname  different ${str%/*}    ><  vs command: >/<

string   aaa
basename same: aaa
dirname  different ${str%/*}    >aaa<   vs command: >.<

string   aaa/
basename different ${str##*/}   ><  vs command: >aaa<
dirname  different ${str%/*}    >aaa<   vs command: >.<

string   aaa/xxx
basename same: xxx
dirname  same: aaa
Run Code Online (Sandbox Code Playgroud)

最有趣的结果之一是$(dirname "aaa").外部命令dirname正确返回.但变量扩展${str%/*}返回不正确的值aaa.

替代演示

脚本:

doit() {
    strings=( "[[$1]]"
    "[[$(basename "$1")]]"
    "[[${1##*/}]]"
    "[[$(dirname "$1")]]"
    "[[${1%/*}]]" )
    printf "%-15s %-15s %-15s %-15s %-15s\n" "${strings[@]}"
}


printf "%-15s %-15s %-15s %-15s %-15s\n" \
    'file' 'basename $file' '${file##*/}' 'dirname $file' '${file%/*}'

doit /aaa/bbb/
doit /
doit /aaa
doit aaa
doit aaa/
doit aaa/xxx
doit aaa//
Run Code Online (Sandbox Code Playgroud)

输出:

file            basename $file  ${file##*/}     dirname $file   ${file%/*}     
[[/aaa/bbb/]]   [[bbb]]         [[]]            [[/aaa]]        [[/aaa/bbb]]   
[[/]]           [[/]]           [[]]            [[/]]           [[]]           
[[/aaa]]        [[aaa]]         [[aaa]]         [[/]]           [[]]           
[[aaa]]         [[aaa]]         [[aaa]]         [[.]]           [[aaa]]        
[[aaa/]]        [[aaa]]         [[]]            [[.]]           [[aaa]]        
[[aaa/xxx]]     [[xxx]]         [[xxx]]         [[aaa]]         [[aaa]]        
[[aaa//]]       [[aaa]]         [[]]            [[.]]           [[aaa/]]       
Run Code Online (Sandbox Code Playgroud)


Adr*_*rth 8

  1. dirname.如果其参数不包含斜杠/,则输出,因此dirname使用参数替换进行仿真不会产生相同的结果,具体取决于输入.

  2. basename将后缀作为第二个参数,它也将从文件名中删除该组件.您也可以使用参数替换来模拟这一点,但由于您不能同时执行这两项操作,因此它不像使用时那样简短basename.

  3. 使用dirname或者basename需要子shell,因为它们不是shell内置的,所以参数替换会更快,特别是在循环中调用它们时(如图所示).

  4. 我已经basename在不同系统(/usr/bin,/bin)上的不同位置看到过,因此如果由于某种原因必须在脚本中使用绝对路径,它可能会因为无法找到可执行文件而中断.

所以,是的,有一些事情要考虑,根据情况和输入,我使用两种方法.

编辑:两者dirnamebasename实际上在源树中可用作bash可加载builtin的,examples/loadables并且可以使用启用(一旦编译)

enable -f /path/to/dirname dirname
enable -f /path/to/basename basename
Run Code Online (Sandbox Code Playgroud)


cda*_*rke 5

使用变量替换的主要缺陷是它们难以阅读和支持.

那当然是主观的!我个人在整个地方使用变量替换.我用read,IFSset不是awk.我使用bash正则表达式,而bash扩展globbing而不是sed.但那是因为:

a)我想要表现

b)我是唯一一个会看到这些剧本的人

令人遗憾的是,许多需要维护shell脚本的人对语言知之甚少.您必须做出平衡决定:哪个更重要,性能还是可维护性?在大多数情况下,您会发现可维护性获胜.

你必须承认这basename $0是相当明显的,而${0##*/}相当模糊