Bash 变量替换与 dirname 和 basename

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22401091/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 09:55:26  来源:igfitidea点击:

Bash variable substitution vs dirname and basename

bash

提问by kobame

The next script

下一个脚本

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

produces:

产生:

str: /aaa/bbb/ccc.txt
ccc.txt == ccc.txt
/aaa/bbb == /aaa/bbb

The question is:

问题是:

  • In bash scripts, when is it recommended to use commands dirnameand basenameand when the variable substitutions and why?
  • 在bash脚本,如果是,则建议使用命令dirnamebasename当变量替代,为什么?

Asking mainly because:

问主要是因为:

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

on my system produces:

在我的系统上产生:

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

Calling external programs (forking) costs time. The main point of the question is:

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

  • Are there some pitfalls using variable substitutions instead of external commands?
  • 使用变量替换而不是外部命令是否存在一些陷阱?

回答by jm666

The external commands make some logical corrections. Check the result of the next script:

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

doit() {
    str=
    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

with the result

结果

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

One of most interesting results is the $(dirname "aaa"). The external command dirnamecorrectly returns .but the variable expansion ${str%/*}returns the incorrect value aaa.

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

Alternative presentation

替代演示

Script:

脚本:

doit() {
    strings=( "[[]]"
    "[[$(basename "")]]"
    "[[${1##*/}]]"
    "[[$(dirname "")]]"
    "[[${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//

Output:

输出:

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/]]       

回答by Adrian Frühwirth

  1. dirnameoutputs .if its parameter doesn't contain a slash /, so emulating dirnamewith parameter substitution does not yield the same results depending on the input.

  2. basenametakes a suffix as second parameter which will also remove this component from the filename. You can emulate this as well using parameter substitution but since you cannot do both at once it is not as brief as when using basename.

  3. Using either dirnameor basenamerequire a subshell since they are not shell builtins, so the parameter substitution will be faster, especially when calling them in a loop (as you have shown).

  4. I have seen basenamein different locations on different systems (/usr/bin, /bin) so if you have to use absolute paths in your script for some reason it might break since it cannot find the executable.

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

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

  3. 使用dirnamebasename需要子shell,因为它们不是shell 内置函数,因此参数替换会更快,尤其是在循环中调用它们时(如您所示)。

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

So, yes, there are some things to consider and depending on situation and input I use both methods.

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

EDIT: Both dirnameand basenameare actually available as bashloadable builtins under examples/loadablesin the source tree and can be enabled (once compiled) using

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

enable -f /path/to/dirname dirname
enable -f /path/to/basename basename

回答by cdarke

The main pitfall in using variable substitutions is that they can be difficult to read and support.

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

That is, of course, subjective! Personally I use variable substitutions all over the place. I use read, IFS, and setinstead of awk. I use bash regular expressions, and bash extended globbing instead of sed. But that is because:

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

a) I want performance

a) 我想要表现

b) I am the only person who will ever see these scripts

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

It is sad to say that many people who have to maintain shell scripts know frightenly little about the language. You have to make a balance decision: which is more important, performance or maintainability? On most occasions you will find that maintainability wins.

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

You have to admit that basename $0is fairly obvious, whereas ${0##*/}is fairly obscure

你不得不承认这basename $0是相当明显的,而这${0##*/}是相当晦涩的