Подстановка переменных 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/]]
dirname
выходы.
если его параметр не содержит косую черту/
подражаяdirname
с подстановкой параметров не дает одинаковых результатов в зависимости от ввода.basename
принимает суффикс в качестве второго параметра, который также удалит этот компонент из имени файла. Вы также можете эмулировать это с помощью подстановки параметров, но поскольку вы не можете сделать оба сразу, это не так кратко, как при использованииbasename
,Используя либо
dirname
или жеbasename
требуется subhell, поскольку они не являются встроенными оболочками, поэтому подстановка параметров будет происходить быстрее, особенно при вызове их в цикле (как вы показали).я видел
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##*/}
довольно неясен