Значення рядка "exec tail -n +3 $ 0" у файлі 40_custom


17

Я намагаюся зрозуміти файли конфігурації grub. Отже, під час цього процесу я зіткнувся з файлом /etc/grub.d/40_custom . Мій файл містить такі рядки:

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
menuentry "Windows 10" --class windows --class os {
insmod part_msdos
savedefault
insmod ntfs
insmod ntldr
set root='(hd0,msdos1)'
ntldr ($root)/bootmgr
}

так як моя система - це подвійне завантаження і, мабуть, це завантажувач для Windows 10.

Моє запитання, хоча це ця частина exec tail -n +3 $0.
Якщо я розшифровую це правильно, це просто означає надрукувати останні рядки, починаючи з 3-го рядка ( +3) файлу $0. $0звичайно, у цьому випадку є власне файл /etc/grub.d/40_custom .

Отже, чому ми використовуємо цю команду у файлі 40_custom ? Як я розумію, результат був би таким самим, якби ιt взагалі було пропущено. Єдине, що я міг би придумати, - це перший рядок, який ідентифікує перекладача:

#!/bin/sh

Але потім воно виконується, оскільки exec tail -n +3 $0слідує за ним. Отже, це просто (марна) конвенція?

Відповіді:


16

Хитрість полягає в тому, що execробить:

$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the shell with the given command.

    Execute COMMAND, replacing this shell with the specified program.
    ARGUMENTS become the arguments to COMMAND.  If COMMAND is not specified,
    any redirections take effect in the current shell.

Це означає, що вона замінить оболонку тим exec, що в цьому випадку надано tail. Ось приклад цього в дії:

$ cat ~/foo.sh
#!/bin/sh
exec tail -n +3 "$0"
echo foo

$ ./foo.sh
echo foo

Отже echoкоманда не виконується, оскільки ми змінили оболонку і використовуємо tailнатомість. Якщо ми видалимо exec tail:

$ cat ~/foo.sh
#!/bin/sh
echo foo
$ ./foo.sh
foo

Отже, це акуратний трюк, який дозволяє писати сценарій, єдиним завданням якого є виведення власного вмісту. Імовірно, будь-який виклик 40_customочікує, що його вміст буде вихідним. Звичайно, тут виникає питання, чому б не просто бігати tail -n +3 /etc/grub.d/40_customбезпосередньо.

Я здогадуюсь, що відповідь полягає в тому, що grub використовується власна мова сценаріїв, і це змушує цього вирішити.


Гарна відповідь! Але що робити, якщо ми напишемо #!/bin/tail -n +2як черепашник? Чи надрукує решту файлу?
Вал каже

@val спробуйте і подивіться :) Виявляється, це не працює ідеально як шебанг, -n +2здається, інтерпретується як -n 2і друкуються лише два останні рядки. Це, мабуть, варто власного питання.
тердон

3
@val ... поведінка shebang набагато менш стандартизована і портативна, ніж ви можете очікувати. Дивіться розділ "інтерпретація командних аргументів" на en.wikipedia.org/wiki/Shebang_(Unix)#Portability
Чарльз Даффі

@val Це працює в Linux, якщо ви видалите пробіл між nі +. Принаймні в Linux, після виконавчого шляху та пробілу, наступні символи трактуються як єдиний аргумент. Тож із простором хвіст бачить це і, ймовірно, вирішує видалити будь-який -nаргумент недійсного префікса (у даному випадку пробіл та а +), поки він не потрапить до числа. Однак, як сказав Чарльз Даффі, теперішній спосіб, яким вони це зробили, є, ймовірно, більш портативним для інших уніцій.
JoL

1
@fluffy Вони можуть використовувати тут док. Дивіться посилання з документації Fedora в моїй відповіді. Вони користуються саме cat <<EOF ...EOFтам
Сергій Колодяжний

9

Каталог /etc/grub.d/містить багато виконуваних файлів (як правило, сценарії оболонки, але можливий також будь-який інший виконуваний тип). Щоразу, коли grub-mkconfigвиконується (наприклад, якщо ви запускаєте update-grub, але також при встановленні оновленого пакета ядра, який зазвичай має гачок після встановлення, який повідомляє менеджеру пакунків оновитись grub.cfg), вони виконуються в алфавітному порядку. Усі їхні результати об'єднуються і потрапляють у файл /boot/grub/grub.cfgіз чіткими заголовками розділів, які показують, яка частина походить із якого /etc/grub.d/файлу.

Цей конкретний файл 40_customпризначений для того, щоб ви могли легко додавати записи / рядки до них grub.cfg, просто ввівши / вставивши їх у цей файл. Інші сценарії в цьому ж каталозі виконують складніші завдання, такі як пошук ядер або нелінуксних операційних систем та створення записів меню для них.

Для того, щоб дозволити grub-mkconfigобробляти всі ці файли однаково (виконувати та приймати вихід), 40_customє сценарієм і використовує цей exec tail -n +3 $0механізм для виведення його вмісту (мінус "заголовка"). Якщо він не був виконуваним, update-grubзнадобиться спеціальне жорстке кодування виняток, щоб взяти буквальний текстовий вміст цього файлу, а не виконувати його, як і всі інші. Але що робити, якщо ви (або виробники іншого дистрибутива Linux) хочете дати цьому файлу інше ім’я? Або що робити, якщо ви не знали про виняток і створили сценарій оболонки з назвою 40_custom?

Ви можете прочитати більше про те grub-mkconfigта /etc/grub.d/*в посібнику з GNU GRUB (хоча він говорить переважно про параметри, які ви можете встановити /etc/default/grub), а також має бути файл, /etc/grub.d/READMEякий визначає, що ці файли виконуються у формі grub.cfg.


1
Хоча відповідь Тердона пояснює, як працює лінія, ця дає більш правдоподібну причину дизайну щодо того, чому GRUB робить так.
JoL

Чудова відповідь. На вашу думку про особливі винятки - "простішим" винятком могло бути два каталоги, наприклад, /etc/grub.d/execдля файлів / скриптів, які можна виконати, і /etc/grub.d/staticдля текстових файлів із звичайним текстом, або якийсь інший показник для їх розрізнення. Однак це було дизайнерське рішення, з яким вони йшли.
Стобор

3

TL; DR : це хитрість спростити додавання нових записів у файл

Вся справа описана на одній із сторінок Wiki Ubuntu на grub :

  1. Лише виконувані файли генерують вихід у grub.cfg під час виконання update-grub.

Виведення скриптів у /etc/grub.d/стає вмістом grub.cfgфайлу.

А тепер що execробити? Він може або повторно підключити вихід для всього сценарію, або якщо передбачена команда - згадана команда наздоганяє і замінює процес сценарію. Колись сценарій оболонки з PID 1234 зараз - це tailкоманда з PID 1234.

Тепер ви вже знаєте, що tail -n +3 $0друкує все після 3-го рядка в самому сценарії. То навіщо нам це робити? Якщо груб піклується лише про результат, ми могли б так само зробити

cat <<EOF
    menuentry {
    ...
    }
EOF

Насправді ви знайдете cat <<EOFприклад в документації Fedora , хоча і з іншою метою. Вся справа в коментарях - простота використання для користувачів:

# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.

З execхитрістю не потрібно знати, що робитьcat <<EOF робити (спойлер, який називається тут-doc ), і не потрібно пам'ятати, щоб додати EOFостанній рядок. Просто додайте меню до файлу і виконайте його. Плюс до цього, якщо ви створюєте сценарій, додаючи меню, ви можете просто додати його >>в оболонці до цього файлу.

Дивись також:


Але чому б не прочитати файли безпосередньо? Чи маєте ви уявлення, чому вони вирішили зробити їх виконуваним замість цього? Набагато простіше було б мати їх як прості текстові файли, які читаються будь-яким процесом, що генерує меню, але натомість вони вирішили зробити їх виконуваним і додали цей (акуратний), але складний спосіб вирішення. Це тому, що у груба є своя мова сценаріїв, як я викладаю у своїй відповіді?
тердон

@terdon Я не думаю, що це має нічого спільного з самою мовою грі. У цьому випадку вам не знадобиться #!/bin/sh. Я підозрюю, що це просто історична причина (перенесена з кодової бази PUPA ) та дизайнерське рішення, натхнене сценарієм типу SysV.
Сергій Колодяжний

@terdon Це насправді надихнуло запитання: unix.stackexchange.com/q/492966/85039
Сергій Колодяжний
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.