Подстановка переменных Bash против dirname и basename

Следующий сценарий

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

производит:

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

Вопрос в том:

  • В скриптах bash, когда рекомендуется использовать команды dirname а также basename а когда переменные подстановки и почему?

В основном потому, что:

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

на моей системе выдает:

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

Вызов внешних программ (разветвление) стоит времени. Основным вопросом вопроса является:

  • Есть ли подводные камни при использовании подстановок переменных вместо внешних команд?

3 ответа

Внешние команды вносят некоторые логические исправления. Проверьте результат следующего скрипта:

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

с результатом

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

Одним из наиболее интересных результатов является $(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//

Выход:

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/]]       
  1. dirname выходы . если его параметр не содержит косую черту /подражая dirname с подстановкой параметров не дает одинаковых результатов в зависимости от ввода.

  2. basename принимает суффикс в качестве второго параметра, который также удалит этот компонент из имени файла. Вы также можете эмулировать это с помощью подстановки параметров, но поскольку вы не можете сделать оба сразу, это не так кратко, как при использовании basename,

  3. Используя либо dirname или же basename требуется subhell, поскольку они не являются встроенными оболочками, поэтому подстановка параметров будет происходить быстрее, особенно при вызове их в цикле (как вы показали).

  4. я видел basename в разных местах на разных системах (/usr/bin, /bin) поэтому, если вам по какой-то причине придется использовать абсолютные пути в вашем скрипте, он может сломаться, так как не может найти исполняемый файл.

Итак, да, есть некоторые вещи, которые необходимо учитывать, и в зависимости от ситуации и ввода я использую оба метода.

РЕДАКТИРОВАТЬ: оба dirname а также basename на самом деле доступны как bash загружаемый builtinпод examples/loadables в исходном дереве и может быть включен (после компиляции) с помощью

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

Основной недостаток использования переменных - это то, что их трудно читать и поддерживать.

Это, конечно, субъективно! Лично я использую переменные замены повсюду. я использую read, IFS, а также set вместо awk, Я использую регулярные выражения bash, а вместо глобальных расширений bash sed, Но это потому что:

а) хочу производительности

б) я единственный человек, который когда-либо увидит эти сценарии

Грустно сказать, что многие люди, которым приходится поддерживать сценарии оболочки, пугающе мало знают о языке. Вы должны принять решение о балансе: что важнее, производительность или ремонтопригодность? В большинстве случаев вы обнаружите, что ремонтопригодность выигрывает.

Вы должны признать, что basename $0 довольно очевидно, тогда как ${0##*/} довольно неясен

Другие вопросы по тегам