shellcheck радить не використовувати базове ім'я: чому?


25

Я випробовую шелчек .

У мене є щось подібне

basename "${OPENSSL}" 

і я отримую наступну пропозицію

Use parameter expansion instead, such as ${var##*/}.

З практичної точки зору я не бачу різниці

$ export OPENSSL=/opt/local/bin/openssl
$ basename ${OPENSSL}
openssl
$ echo ${OPENSSL##*/}
openssl

Оскільки basenameє в специфікаціях POSIX , я не є причиною, чому це повинно бути найкращою практикою. Будь-який натяк?


8
Він розщеплює новий процес, коли цього не потрібно.
jordanm

@jordanm досить справедливо ... Я не думав про ефективність.
Маттео

3
@jordanm З іншого боку, він працює з снарядами, крім башма
Маттео

1
@JosephR. це те, що я думав, але тільки що з’ясував, що це не працює csh. Я думаю, cshце не POSIX.
тердон

3
@terdon - csh дуже далеко від POSIX.
Йорданм

Відповіді:


25

Справа не в ефективності - це в правильності. basenameвикористовує нові рядки для розмежування імен файлів, які він виводить. У звичайному випадку, коли ви передаєте лише одне ім'я файлу, він додає у вихідний результат нову рядок. Оскільки назви файлів можуть містити самі нові рядки, це ускладнює правильну обробку цих імен.

Це ускладнюється ще й тим , що люди , як правило , використовують basenameтак: "$(basename "$file")". Це ще більше ускладнює ситуацію, оскільки $(command)знімає всі лінійки command. Розглянемо малоймовірний випадок, який $fileзакінчується новим рядком. Тоді basenameви додасте додатковий новий рядок, але "$(basename "$file")"знімете обидва нові рядки, залишаючи вам неправильне ім’я файлу.

Інша проблема basenameполягає в тому, що якщо $fileпочинається з -(тире з мінусом), це буде інтерпретуватися як варіант. Це легко виправити:$(basename -- "$file")

Надійний спосіб використання basename:

# A file with three trailing newlines.
file=$'/tmp/evil\n\n\n'

# Add an 'x' so we can tell where $file's newlines end and basename's begin.
file_x="$(basename -- "$file"; printf x)"

# Strip off two trailing characters: the 'x' added by us and the newline added by basename. 
base="${file_x%??}"

Альтернативою є використання ${file##*/}, яке простіше, але є свої помилки. Зокрема, це неправильно у випадках, коли $fileє /або foo/.


2
Дуже хороші бали, +1. Радий, що ОП прийняла це замість мого.
terdon

2
Контрапункт: що робити , якщо $fileє foo/? Що робити, якщо це /?
Жил "ТАК - перестань бути злим"

2
@Gilles: Ти маєш рацію. Я починаю думати, що basenameпідхід, зрештою, кращий, такий же покірливий. Кращі альтернативи я можу придумати це ${${${file}%%/#}##*/}і [[ $file =~ "([^/]*)/*$" ]] && printf "%s" $match[1], обидва з яких є ЗШ-специфічні і ні один з яких ручок /правильно.
Метт

@terdon Дякую, що ти не сприйняв це особисто :-). Файли з новими рядками зустрічаються нечасто, але в Метта є крапка. Звичайно, використання змінної заміни також є більш ефективним.
Маттео

16

Відповідні рядки в shellcheck«S вихідний код , є:

checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "dirname" =
    style id "Use parameter expansion instead, such as ${var%/*}."
checkNeedlessCommands (T_SimpleCommand id _ (w:_)) | w `isCommand` "basename" =
    style id "Use parameter expansion instead, such as ${var##*/}."
checkNeedlessCommands _ = return ()

Немає checkNeedlessCommandsясного пояснення, але виходячи з назви функції ( ), схоже, @jordanm цілком правильно, і це пропонує вам уникати форсування нового процесу.


7
Нехай джерело буде з вами :)
Джозеф Р.

3

dirname, basenameі readlinkт. д. (спасибі @Marco - це виправлено) може створити проблеми з портативністю, коли безпека стане важливою (вимагає безпеки шляху). Багато систем (наприклад, Fedora Linux) розміщують його, /binтоді як інші (наприклад, Mac OSX) розміщують його /usr/bin. Потім є Bash на Windows, наприклад, cygwin, msys та інші. Завжди краще залишатися чистим Башем, коли це можливо. (за коментарем @Marco)

До речі, дякую за вказівник на оболонку, я цього раніше не бачив.


1
1) Що ви маєте на увазі під «безпекою»? Чи можете ви докладно? 2) ОП взагалі не згадується dirname. 3) Основні утиліти основних ядер повинні бути в PATH, де б вони не зберігалися. Я ще не бачив системи, де basenameне було в ПАТ. 4) Якщо припустити, що баш доступний - це питання переносності. Завжди краще триматися подалі від bash і використовувати оболонку POSIX, коли потрібна портативність.
Марко

@Marco Дякуємо, що вказали на ці проблеми. Так, ви правильні, що утиліти на шляху. Але там, де хочеться забезпечити додаткову безпеку сценарію Bash, корисно надати абсолютний шлях. Отже, виклик "/ bin / basename" буде працювати для систем RedHat, але це призведе до помилки на Mac. Це краще пояснено у кулінарній книзі Bash, де цій чверті присвячено приблизно чверть із 600 сторінок. Ми зробили грубу спробу вирішити проблеми безпеки в нашому безкоштовному захищеному джерелі .
AsymLabs

@ Марко Пункт 4 є вагомим коментарем, але питання (ОП) починається і записується навколо оболонки, яка призначена для перевірки обох сценаріїв sh / bash. Тому ми маємо припустити, що це не зовсім Posix за замовчуванням.
AsymLabs

@Marco Безпека змінної середовища середовища може бути легко порушена. Наприклад, я був дуже здивований, коли кілька років тому виявив, що Ruby RVM, встановлений локально, за замовчуванням розмістив його шлях до системного шляху.
AsymLabs

ОП спеціально згадує POSIX, і питання позначене тегом, posixа не bash. Я не можу знайти жодного показника того, що ОП вимагає баш-рішення. Твоя заява "Завжди краще залишатися чистим Башем" якраз неправильна, мені дуже шкода.
Марко
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.