Комментарии 201
root@8e93b4d16cc3:/# cat 1.sh
#!/bin/bash
sleep 3
#rm -rf --no-preserve-root /
echo "Time's up!"
root@8e93b4d16cc3:/# time bash 1.sh
Time's up!
real 0m3.010s
user 0m0.002s
sys 0m0.003s
root@8e93b4d16cc3:/# ls -lha /
total 92K
drwxr-xr-x 1 root root 4.0K May 7 07:37 .
drwxr-xr-x 1 root root 4.0K May 7 07:37 ..
-rwxr-xr-x 1 root root 0 May 7 07:36 .dockerenv
-rw-r--r-- 1 root root 67 May 7 07:37 1.sh
drwxr-xr-x 2 root root 4.0K Apr 3 17:14 bin
drwxr-xr-x 2 root root 4.0K Apr 24 2018 boot
drwxr-xr-x 5 root root 360 May 7 07:36 dev
drwxr-xr-x 1 root root 4.0K May 7 07:36 etc
drwxr-xr-x 2 root root 4.0K Apr 24 2018 home
drwxr-xr-x 1 root root 4.0K May 23 2017 lib
drwxr-xr-x 2 root root 4.0K Apr 3 17:13 lib64
drwxr-xr-x 2 root root 4.0K Apr 3 17:12 media
drwxr-xr-x 2 root root 4.0K Apr 3 17:12 mnt
drwxr-xr-x 2 root root 4.0K Apr 3 17:12 opt
dr-xr-xr-x 187 root root 0 May 7 07:36 proc
drwx------ 1 root root 4.0K May 7 07:37 root
drwxr-xr-x 1 root root 4.0K Apr 24 01:07 run
drwxr-xr-x 1 root root 4.0K Apr 24 01:07 sbin
drwxr-xr-x 2 root root 4.0K Apr 3 17:12 srv
dr-xr-xr-x 13 root root 0 May 7 07:37 sys
drwxrwxrwt 1 root root 4.0K May 7 07:36 tmp
drwxr-xr-x 1 root root 4.0K Apr 3 17:12 usr
drwxr-xr-x 1 root root 4.0K Apr 3 17:14 var
Вот перепроверил, таки да, поведение не изменилось. После изменения на 3 секунды, процесс продолжил работать 30 секунд и выплюнул Time's up!
Сколько помню, практически у любого шелла, не использующего прозрачную компиляцию в байт-код, изменение скрипта во время выполнения приводит к ошибке в следующей команде. Что на bash, что на ksh, что на прочих всяких.
y@as:~/test$ bash --version
GNU bash, version 5.0.3(1)-release (i686-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
y@as:~/test$ cat cp1.sh
#!/bin/bash
echo Sleeping...
sleep 30
# echo nonono
echo done
y@as:~/test$ ./cp1.sh
Sleeping...
nonono
done
bash --version
bash --version
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
root@b6d513518e23:/# cat 1.sh
cat 1.sh
#!/bin/bash
sleep 90
#rm -rf --no-preserve-root /
echo "Time's up!"
root@b6d513518e23:/# time ./1.sh
Вот изменение его в процессе работы, спецом с выводом ps
root 508 0.0 0.0 18616 3504 pts/0 S 10:37 0:00 bash -v
root 531 0.0 0.0 18376 3080 pts/0 S+ 10:38 0:00 \_ /bin/bash ./1.sh
root 532 0.0 0.0 4532 736 pts/0 S+ 10:38 0:00 \_ sleep 90
root@b6d513518e23:/# cat 1.sh
#!/bin/bash
sleep 9
#rm -rf --no-preserve-root /
echo "Time's up!"
А вот вам результат отработки скрипта
time ./1.sh
Time's up!
real 1m29.900s
user 0m0.002s
sys 0m0.003s
А nano исходный изменяет…
По умолчанию — если у файла нет «специальных» аттрибутов и/или двух имён — заменяет. Дайте вашему скрипту два имени и, внезапно, с VIM тоже всё воспроизведётся.
Это особенно «приятно» для тех, кто не любит читать документацию, а постигает всё вокруг только путём экспериметов. Потому что они запомнят, что с VIM — нет проблем… и поимеют их в самый неподходящий момент потом.
Поставлю одну точку над Ё. При отсутствии символических ссылок:
strace vi .bashrc 2>strace.log
В логе присутствует:
rename(".bashrc", ".bashrz~") = 0
unlink(".bashrz~") = 0
При наличии hadrlink — поведение другое.
этот linux настолько неудобен, что даже выстрелить себе в ногу можно лишь в специальных условиях
</sarcasm>
а по факту — nano не такой распространенный инструмент на самом деле.
Те, кто боле-мене долго работают в linux — используют vim
/emacs
Те, для кого linux все еще экзотика, использют mc
и соответственно — mceditor
(который тоже через временные файлы и переименование работает)
те, кто привык к классике и не понимает этих новомодных вещей, выбирают ed
(интересно — остались ли еще такие?)
вот им да, им, скорее всего, придется столкнуться с той же проблемой
Те, для кого linux все еще экзотика, использют mc и соответственно — mceditor
Заменил винду на линукс на основном компе лет 10 назад, пользуюсь mc
и mcedit
для редактирования всего (в т.ч. кода, вместо IDE), vi
использую только когда mcedit
недоступен, а emacs
когда-то 1 раз попробовал и больше не пытался.
Так что не надо за всех говорить.
Так будет правильнее.
Достаточно пройти vimtutor за 15 минут
Единственное место, где vim более-менее необходим — это редактирование файлов со смартфона по SSH, в nano не очень удобно, но не критично, когда есть эмуляция клавиши Ctrl.
Ну а если переходить к вопросу, зачем — то я, например вообще не могу в nano работать — настолько отстойные и неудобные комбинации клавиш, вот зачем он мне, если vim намного удобнее?
Ну а если переходить к вопросу, зачем — то я, например вообще не могу в nano работать — настолько отстойные и неудобные комбинации клавиш, вот зачем он мне, если vim намного удобнее?
++++ К тому же nano есть не везде (ну, ок — есть почти везде, в частности, в дебианах-убунтах), а вот vi/vim увидеть в базе вероятность 99.9%
Ещё можно настроить vim на автоматический insertmode — получится то же самое. Только выходить на одну клавишу дольше:) вместо Z,Z будет Ctrl+O,Z,Z. А можно просто нажимать Insert на входе :)
А когда на виртуалке ставил в сети с прокси (не разобрался лично, как настроить) — пришлось за неимение nano юзать vi или что там дефолтное может быть.
Как бы ни хотелось в очередной раз осудить убунту, но nano в качестве дефолтного редактора там унаследован из дебиана. Хуже того, в mc по дефолту снята галочка "use internal editor" и этот дурацкий nano пытается запуститься по F4.
Не знаю зачем они его продвигают.
А ещё в том же дебиане у vi сломаны стрелки для перемещения курсора в insert-режиме (и в текстовой консоли, и в локально запущеном xterm, и по ssh как с другого линукса так и из putty, причем проявляется это во всех версиях дебиана которые я видел) — из-за этого при неустановленном mc приходится таки пользоваться nano (желания искать в каком конфиге они накосячили, или же это баг их сборки vi — нет). При этом в CentOS и FreeBSD эти кнопки работают адекватно во всех сценариях.
А правил конфиги я только для одного — установить туда в дефолт запуск Gnome/xfce. Но наверное лучше будет поставить Xorg и запускать вручную то, что прописано в ~/.xinitrc.
P.S. Ну хорошо, я соврал. Один раз ставил на реальную железку Openmediavault и рулил им через web-морду.
ЕМНИП, я сначала вообще пробовал редактировать «на лету» html-страничку, запущенную в httpd. Но это точно невозможно.
Хм, почему??
Очевидно, потому что в исходном заявлении написана какая-то бессвязица. А реализовывать бессвязицу невозможно.
Тем более такого не будет поведения, работающий процесс, уже замапил в память файл скрипта, изменение его в файле, для работающего процесса уже ничего не меняет.
Все прекрасно меняется. Я с этим сталкивался. Была cron-задача, которая запускала баш-скрипт. Кто-то в это время обновлял скрипт, и если задача уже была запущена, всё сыпалось с ошибками, которые содержали кашу из кусков команд.
работающий процесс, уже замапил в память файл скрипта
Как так? Изменения, вносимые в файл, дложны как раз и лечь сначала в файловый кеш, и все процессы их увидят, если только мы перед открытием не сделали снешпонт ФС и не открыли файл с него. Во всяком случае, man 2 open на Linux не предлагает флага, позволяющего «заморозить» содержимое файла.
а я думал, что есть флаг для эксклюзивного открытия файла на запись, но, видимо, не в этом мире, ога
у open() точно нет.
Или мы про flock/fcntl?
видимо, в мире POSIX по-другому не умеют :-/
видимо, в мире POSIX по-другому не умеют :-/
Ога. Причём на уровне стандартной библиотеки можно сделать хоть чёрта лысого, прочитать весь файл в буфер и показывать кажому процессу свою копию, но и такого нет. Видимо, никому не нужно. Я бы тоже так делать не стал, потому что получится слишком много неопределённости.
Я бы тоже так делать не стал, потому что получится слишком много неопределённости.Вы бы не стали, а вот разработчики Linux — стали. Потому что скрипт ладно, а вот если вы будут менять исполняемый файл какой в то время, как он используется — плохо может быть.
Отсюда и всем известное «Text File Busy».
видимо, в мире POSIX по-другому не умеют :-/Умеют. Ну это если не тыкать в рандомные места, а думать.
Если вы хотите чего-то кода-то «замапить», то вы будете это делать через mmap (внезапно, да?) — и там как раз есть нужный вам флаг
MAP_PRIVATE
.Ну… строго говоря it is unspecified whether modifications to the underlying object done after the MAP_PRIVATE mapping is established are visible through the MAP_PRIVATE mapping, но в Linux вы получите «Text File Busy».
Так что описываемого поведения добиться можно… но вот только
bash
так не делает — но это уже личное дело bash.Но так как это всего лишь упрощение начальной блокировки, Linux решил не принимать.
Почему-то не удалось такое воспроизвести.
> bash --version
GNU bash, version 5.0.16(1)-release (x86_64-pc-linux-gnu)
Вместо корня пытался удалить пустой файлик, но он остался на месте. Лог strace очень большой. Поделиться?
#!/bin/bash
sleep 30
#/bin/echo oopps
echo "Time's up!"
Воспроизводится.
GNU bash, version 4.4.20(1)-release
В старых редакторах обычно есть куча опций (вот, например в emacs), новые часто даже не упоминают о том, как они, собствнно, сохраняют файл. Несмотря на то, что это важно. Не только когда вот так на
bash
издеваются, но, и, например, когда у файла, который вы редактируете — несколько имён (жёсткая ссылка).Можно для воспроизводимости использовать ed (sed -i
, как я сегодня узнал, пишет во временный файл, а потом заменяет им старый, поэтому не подходит).
Примерно так: printf '3s/02/2/\nw\n' | ed -s test.sh
.
$ ed -V
GNU Ed 1.10
...
$ bash --version
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
...
$ cat ./test.sh
#!/bin/bash
echo start
sleep 02
# ls -lh # вместо опасной команды
echo finish
sed -i '3s/ 2$/ 02/' test.sh # чтобы он сам себя восстанавливал обратно
$ ./test.sh &; sleep 1; ( printf '3s/02/2/\nw\n' | ed -s test.sh )
[1] 23942
start
$
total 8,0K
-rwxrwxr-- 1 anton0xf anton0xf 81 июн 3 00:10 test.sh
finish
[1] + done ./test.sh
Наверное, потому что это write once язык. Десяток лет назад он был крут. А сейчас его полностью заменяет петухон. Простите, python 3. Да, производительность Пайтона хуже. Но зато код существенно более простой в поддержке, нет не гемора с модулями ("батарейки включены") и все прочее.
Самому мне индифферентно. Все плохо. Но факт есть — код на перле, который я ковырял — был дикой смесью перла, шелла и нес в себе 100500 уязвимостей и поддерживать его было болью
Я писал в свое время код на перле, писал много, и наш код выглядел на порядки лучше, чем я сейчас читаю на «правильных» языках. Просто писать надо уметь.
Справедливости ради, я тоже писал, и тоже вполне читабельный и поддерживаемый код (на CPAN до сих пор куча модулей лежит), но… это требовало значительно бо́льших усилий, чем на многих других языках. И далеко не все Perl-программисты утруждали себя прикладыванием необходимых для этого усилий. Да, на Perl можно писать чисто, но его репутация write-only language честно заработана.
У «редакторов» работа такая —
И они эту работу выполняют хорошо (видимо).
Так что ждем увлекательный цикл статей об опасности деления на ноль.
Гуглепереводы, конечно же (а то у нас эта практика не слишком распространена).
Правда там не о bash и скриптах, а об арматурине и бензопиле.
Но принцип 1-в-1.
Статья о том, что при изменении скрипта, который выполняется, возможны неожиданные и неочевидные эффекты. А анекдот про недокументированные возможности ;)
Ну тогда пусть попробует выдернуть винт из работающего компьютера. Фейерверк будет еще круче. Главное — неожиданно и неочевидно, ага.
Логика? Не, не слышал.
PS. напомнило анекдот про Петьку на лабораторной по биологии: «Выводы: таракан без ног не слышит».
пусть попробует выдернуть винт из работающего компьютера
Месье никогда не слышал про горячую замену? Некоторые винты из некоторых компьютеров можно выдергивать.
Когда к этому привыкаешь, иное поведение вполне может оказаться сюрпризом в процессе отладки.
Про perl не помню, а питон прекомпилируется в байткод, который и исполняется.
Поэтому изменение исходников питона на исполнении не сказывается.
В отличие от скриптов баша.
Если эти вещи знать и держать в голове, то сюрпризов не будет.
Это да.
Могу предположить, что если (!) дескриптор скрипта остается открытым во время выполнения, то одни редакторы ловят это дело и пишут новую версию в другой файл с тем же именем (а исходный же файл продолжает работать и после закрытия дескриптора просто пропадает).
А другие редакторы пишут прямо поверх.
Вопрос: разумнее знать и помнить какой редактор как работает с дескрипторами файлов — или же просто запомнить "не лезь руками в баш-скрипты на ходу"?
Могу предположить, что если (!) дескриптор скрипта остается открытым во время выполненияТаких редактором науке неизвестно. Но вот VIM, как уже обсуждалось ведёт себя по разному в зависимости от того, одно имя у файла или несколько (да-да, на современных системах у файла может быть больше, чем одно имя).
Там речь не столько о *нихе, сколько о extX.
Винда тоже умеет хардлинки (в ntfs).
Но в данном случае речь скорее не о работе собственно баша, а о том, как вим работает с хардлинками (например "разыменовывает ссылки", образно говоря).
Учитывая, что появился Unix в начале семидесятых прошлого века, фраза, что несколько имён у файла может быть в современных ОС, выглядит забавно.
Про perl не помню,
В перле тоже байт-код. Даже модуль есть специальный для работы с байт-кодом — B::Bytecode.
. Ну, хотя бы у тех же перла и питона, как ближайших альтернатив shell-скриптам.
Это не так, потому что ближайшие к shell скриптам это виндовский .bat файлы.
А bat файлы, как раз, работают именно так. По крайней мере в windows-7 еще считывают максимум +1 строку вперед, и при редактировании запущенного скрипта, вы можете изменить его поведение
Вот за PowerShell не скажу. В смысле не знаю как он себя ведет.
Ну, хотя бы у тех же перла и питона, как ближайших альтернатив shell-скриптам.
Я бы не сказал, что Bat-файлы ближе к shell-скриптам.
Ну вы уж определитесь, что к баш скрипту ближе.
Я говорил именно в контексте, что сравнивать шелл скрипты с языками программирования некоректно, надо сравнивать с другими «шелл» скриптами
Ну вы уж определитесь, что к баш скрипту ближе.
Для начала лучше определимся, что я вообще не упоминал конкретно баш. И хотя да, речь была про никсовые шеллы, cmd тоже вполне себе командная оболочка и bat — скрипт его.
Потом еще определимся, что я не говорил про близость ЯПов к shell-скриптам. Я говорил про ближайшие альтернативы, а при выборе чем проще/лучше автоматизировать очередную задачу, bash/zsh/<по вкусу>sh и тот же питон друг к другу радикально ближе, чем батник. И да, это все еще в первую очередь про никсы.
котенку с дверцейПочему-то сперва подумал про Котобуса.
# cat test.sh; echo '--'; strace -s 256 -f -e openat,read,dup2,lseek ./test.sh & sleep 5; sed -i -re 's|sleep 30|sleep 3|g' test.sh ; fg
#!/bin/bash
sleep 30
#echo "test!!"
echo "end"
--
[1] 19417
read(3, "\177ELF\2\1"..., 832) = 832
read(3, "\177ELF\2\1"..., 832) = 832
read(3, "\177ELF\2\1"..., 832) = 832
read(3, "MemTotal: "..., 1024) = 1024
lseek(3, 0, SEEK_CUR) = 0
read(3, "#!/bin/bash\nsleep 30\n#echo \"test!!\"\necho \"end\"\n", 80) = 47
lseek(3, 0, SEEK_SET) = 0
dup2(3, 255) = 255
lseek(255, 0, SEEK_CUR) = 0
read(255, "#!/bin/bash\nsleep 30\n#echo \"test!!\"\necho \"end\"\n", 47) = 47
lseek(255, -26, SEEK_CUR) = 21
strace: Process 19421 attached
[pid 19421] read(3, "\177ELF\2\1"..., 832) = 832
[pid 19421] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=19421, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
read(255, "#echo \"test!!\"\necho \"end\"\n", 47) = 26
end
read(255, "", 47) = 0
+++ exited with 0 +++
# cat /mnt/test.sh
#!/bin/bash
cat /mnt/test.sh > /mnt/test2.sh
cat /mnt/test2.sh >> /mnt/test.sh
echo test_msg
sleep 3
На centos 7 тоже не воспроизводится. Видимо нужны какие то особые условия.Никаких «особых условий». Просто редактор, которые редактирует-таки файл. А не удаляет его, создавая на его месте другой…
Если вы хотите проверить данное поведение через скрипт, делайте так:
#!/bin/bash
echo '#!/bin/bash
echo "Start and waiting for ten sec $$"
sleep 10
#echo "this should be comment $$"
echo "finished $$"
'> tmp.sh
chmod 755 tmp.sh
./tmp.sh &
sleep 1
echo '#!/bin/bash
echo "Start and waiting for ten sec $$"
sleep 1
#echo "this should be comment $$"
echo "finished $$"
'> tmp.sh
Bash 4.3, Ubuntu 18- не получилось воспроизвести
Bash 5.0, Debian Bullseye — не получилось
Юзаю vim, мб у тебя какой то редактор злой и слешн удаляет?
Если редактировать sublime text 3 тоже самое.
На новом focal воспроизводится.
bash 5.0-6ubuntu1 amd64 GNU Bourne Again SHell
Боже, зачем так пишут? Это бездна.
Впрочем, ещё одна причина яростно нелюбить баш и шеллопрограммирование.
Да, я сделал её с помощью dpkg -l|grep bash
, а что?
bash — интерпретатор команд, считывает скрипт покомандно, по одной (если команда записана в одну строку) или несколько строк. if-then-else будет полностью считана и распарсена перед выполнением (поэтому выполнить else вместо if правкой файла не получится).
Чтение из файла — частный случай, в общем-то, команды могут поступать из входного потока, и будут выполняться по мере поступления, без ожидания конца. И поведение это документировано ( см ниже habr.com/ru/post/500832/#comment_21584918 ), там-же написано зачем это сделано и как писать скрипт, чтобы избежать этой проблемы. Но кому нужна эта документация?
Я знаю, что такое баш, спасибо. Мой комментарий был про сам баш. Зачем так пишут — я про эпоху. garbage in, garbage out, undefined behavior это путь к остроумным оптимизациям, etc, etc.
Каждый раз, когда я макаюсь в сайд-эффекты этого подхода мне становится больно.
Боже, зачем так пишут? Это бездна.Да вы что?! Я как-то делал мутирующий bash-скрипт, который изменял сам себя в процессе работы, а потом, таким же образом, возвращал оригинальные данные файла обратно. Самоизменяющийся код, теперь и в ваших скриптах!
Ну, это какой-то очень грязный метод. Функцию там переопределить — это да. А вот чтобы полагаться на указатель в открытом файле...
Интересно, можно добиться, чтобы if возвращал значение для then ветки, а при этом имел сайд-эффект выполнения else ветки? Просто для bash-wtf'а.
Даже более того, этот метод (полагаться на указатель, без самомодификации) широко используется в sh-архивах. Это когда в начале файла скрипт, который сделан так, чтобы шелл читал его до определённого байта (и это работает стабильно и без неожиданностей), а скрипт этот забирает stdin дескриптор себе, читает из него хвост и распаковывает, а затем может выполнить ещё какие-то действия, например для первоначальной настройки только что распакованного софта. Работает не только с файлами но и с пайпами в стиле curl | bash (точнее stdin как раз для второго случая, а в первом альтернативный способ какой-то). Естественно это всё не только для баша а для большинства шеллов.
скрипт, который сделан так, чтобы шелл читал его до определённого байта
Обычно до определенной строки, хотя это неважно. Никто в здравом уме не правит эти скрипты «на лету». А вот скрипты cron это комбо — и запускаются автоматически, и с большой вероятностью от root, и некоторые достаточно долгоиграющие.
Да, баш читает файл порциями.
НО размер порции выбирает операционная система и она не будет в таком случае инвалидировать файловый кеш для данного фрагмента. А выдаст тот фрагмент, который в буфер уже прочитан.
Да, когда буффер закончится возможно перечитает.
Рискну предположить, что у тех, у кого не воспроизводится, редактор пишет изменённый код в новый файл, который затем переименовывает поверх старого (чтобы гарантировать атомарность обновления). В таком случае у баша останется дескриптор старого файла, содержимое которого не менялось.
sed, gedit — не воспроизводится
А я о чём:
./ OPEN test.sh
./ CREATE sedGpzLhh
./ OPEN sedGpzLhh
./ ACCESS test.sh
./ ATTRIB sedGpzLhh
./ ATTRIB sedGpzLhh
./ CLOSE_NOWRITE,CLOSE test.sh
./ MODIFY sedGpzLhh
./ CLOSE_WRITE,CLOSE sedGpzLhh
./ MOVED_FROM sedGpzLhh
./ MOVED_TO test.sh
./ OPEN test.sh
./ CREATE .test.sh.swp
./ OPEN .test.sh.swp
./ CREATE .test.sh.swx
./ OPEN .test.sh.swx
./ CLOSE_WRITE,CLOSE .test.sh.swx
./ DELETE .test.sh.swx
./ CLOSE_WRITE,CLOSE .test.sh.swp
./ DELETE .test.sh.swp
./ CREATE .test.sh.swp
./ OPEN .test.sh.swp
./ MODIFY .test.sh.swp
./ MODIFY .test.sh.swp
./ ATTRIB .test.sh.swp
./ CLOSE_NOWRITE,CLOSE test.sh
./ OPEN test.sh
./ ACCESS test.sh
./ CLOSE_NOWRITE,CLOSE test.sh
./ OPEN,ISDIR
./ CLOSE_NOWRITE,CLOSE,ISDIR
./ OPEN,ISDIR
./ CLOSE_NOWRITE,CLOSE,ISDIR
./ OPEN,ISDIR
./ CLOSE_NOWRITE,CLOSE,ISDIR
./ MODIFY .test.sh.swp
./ MODIFY .test.sh.swp
./ OPEN test.sh
./ MODIFY test.sh
./ ATTRIB test.sh
./ CLOSE_WRITE,CLOSE test.sh
./ ATTRIB test.sh
./ MODIFY .test.sh.swp
./ CLOSE_WRITE,CLOSE .test.sh.swp
./ DELETE .test.sh.swp
Извините, gedit не проверю — у меня Plasma, он не установлен.
Самый простой способ проверки, что именно делает редактор:
$ ls -i test.sh
33702972 test.sh
$ vim test.sh # edit the file
$ ls -i test.sh
33702972 test.sh
$ sed -e 's/10/20/' -i test.sh
$ ls -i test.sh
33717589 test.sh
если inode изменяется, то редактор заменил старый файл новым; если остаётся прежним — отредактировал файл на месте.
После того как я меняю значение с 30 секунд на 2, и сохраняю файл (вимом)
вывод ls -i меняется, да.
Но! Если я поставлю назад 30 вместо 2 и сохраню, вывод ls -i опять покажет то значение, которое было до изменения, а по Вашему — оно должно изменится еще раз)
Ну, или я что-то делаю не так)
Похоже, в моей версии vim (8.2) есть специальная логика для предотвращения таких проблем, как в статье:
~ % ls -i test.sh
27402937 test.sh
~ % vim test.sh
~ % ls -i test.sh
27403483 test.sh
~ % vim test.sh
~ % ls -i test.sh
27402937 test.sh
Свапаются два файла. Можно перезаписать оригинальный двойным сохранением. А теперь запустим скрипт во время редактирования:
~ % ls -i test.sh
27402937 test.sh
~ % vim test.sh # save twice
~ % ls -i test.sh
27403538 test.sh
Если редактируемый файл открыт кем-то ещё, то создаётся новый файл.
Забавно, мой vim 8.2 номер инода не меняет никогда. Возможно, какие-то детали настроек — у меня он практически без кастомных настроек, я им не пользуюсь особо.
vim
.Хотя там, во-первых, всё описано, в том числе, внимание, описаны настройки, которые на это влияют…
Сегодня же предложение это сделать воспринимается чуть ли не как личное оскорбление.
Например, раньше не было публикаций «Х причин почему не Линукс» (особенно до появления графических инсталляторов, разве что «почему не linux а freebsd»). Тут, конечно, отличия налицо.
Но, к примеру, статей типа «Y причин, почему я выбираю Windows, а не FreeBSD» как не было так и нет, так что тут все ок.
Увы, да — я открыл себе исходный код на посмотреть когда-нибудь потом, не подумав, что это должно бы быть задокументировано.
Скорей всего, так сделано для того чтобы шелл мог выполнять многомегабайтные скрипты не тратя на это память. В ранних unix-ах это было актуально, когда у тебя память измеряется в килобайтах.
В zsh
, кстати, не воспроизводится, видимо при загрузке он целиком считывает файл скрипта.
Это нужно и сейчас, для работы самораспаковывающихся архивов, например AppImage.
zsh не спасёт, даже если не воспроизводится. Частично потому, что у zsh такая тьма опций (в т.ч. для совместимости с другими шеллами), что не удивлюсь, если найдётся несколько таких, включение которых сделает так, что начнёт воспроизводиться. Но главная причина в том, что скрипты на zsh редко пишут даже те, кто им пользуется — обычно для лучшей совместимости все скрипты пишут либо на bash либо на приближении к POSIX sh.
(промахнулся местом ответа)
В общем случае все зависит от того, как именно текстовый редактор сохраняет файл.
Имеется два варианта:
1. Уже имеющийся файл открывается на запись и переписывается. Так работают nano и mcedit.
2. Старый файл переименовывается, на его место пишется новый. Как вариант — новый файл пишется рядышком как временный, затем старый удаляется, временный переименовывается на его место. Так работает, например, vim.
В первом случае поведение воспроизводится, во втором — нет:
# cp1.sh - ждем 30 сек, cp2.sh - ждем 3 сек
# запускаем cp1.sh
# в соседней консольке:
$ cp cp2.sh cp1.sh
# Воспроизводится!
# Теперь переименование:
# cp1.sh - ждем 30 сек, cp2.sh - ждем 3 сек
# запускаем cp1.sh
# в соседней консольке:
$ mv cp1.sh cp1.old; cp cp2.sh cp1.sh
# Не вопроизводится.
Вот один из вариантов:
#!/bin/bash
{
# код тут
}; exit;
Интерпретатор принудительно прочитает скрипт до закрывающей фигурной скобки, включая команду exit(важно, чтобы она была на той же строке)
sed 's/0//' <<<$(<test.sh) >test.sh
или даже так
grep -o '[^0]\+' <<<$(<test.sh) >test.sh
Автору оригинала — жирный плюс в карму (за пытливый ум) и долгих лет жизни (что б продолжал радовать)
кому-то приходит в голову редактировать запущенный скрипт?
внезапно — этот скрипт может быть частью какой-то сложной системы. И запускаться, например, по крону. И именно в этот момент дернуло этот скрипт обновить (через ансиболь какую-нибудь)...
Причем тут нано? Опасность не в нано, а в изменении текста скрипта в момент выполнения. Более того — во всякие анзиблы регулярно вносят PR, чтобы они каким-то особым образом обрабатывали айноды в ФС (либо перезатирали файлы, либо создавали их заново + атомарный свап). И наверняка сказать, что там под капотом без вникания в доку или в исходники Вы не сможете.
специально проверил cp и mv — не срабатывает. собственно, комментарии выше про vim подтверждают это
ну а если пишите в потенциально запущенные скрипты, не прибив их при этом,…
Вы совершенно не уловили сути.
а ансибла можно попросить rm…;…; cp…; mv… в начале можно добавить ждать завершения либо прибить (по ситуации).
Ну, два вопроса.
- Много ли людей будут делать такую многоходовочку?
- Ансибл имеет модуль работы с файлом. Вместо этого секса с шеллом в несколько ступеней Вы наверняка возьмёте модуль и тупо замените файл.
ну а если пишите в потенциально запущенные скрипты, не прибив их при этом,…
Внезапно — половина софта (не скрипты) прекрасно обновляется на ходу. И никаких проблем.
А вы попробуйте бинарник (не скрипт) хотя бы того же sleep скопиривать куда-нибудь, оттуда запустить и попытаться отредактировать.
Спойлер: я даже знаю какую ошибку вы получите при попытке сохранения бинарника, начинается она на "text".
Так что любая здравомыслящая система обновления пакетов будет писАть временный файл, удалять старый и на его место перемещать новый.
И, кстати, не весь софт даже так переживает обновление без рестарта — firefox вот регулярно требует его рестартауть после обновления системы, хотя раньше к этому спокойно относился.
Выше я приводил пример скрипта, который выполняет закомментированную строчку.
Очевидно, что так делать не следует.
очевидно, что можно не давать очевидные советы. Это РЕАЛЬНО может произойти в РЕАЛЬНО работающей системе.
Аналогия некорректная. Если ее дальше развивать, то вообще компьютером пользоваться нельзя. На самом деле проблем с файлами существенно больше. Например, с logrotate — там вообще есть специальные опции, чтобы учитывать особенности поведения записи в лог некоторых програм.
Есть множество ситуаций, когда обновляется скрипт, который может сейчас выполняться.
И как это поможет, если старый файл как бы удален, а новый файл записан с именем старого (как я понимаю айнода будет новая)?
Для данной задачи? Никак, любой способ проверки подвержен проблеме "был запущен между проверкой и сохранением". Правильный подход здесь — скопировать, отредактировать, переместить на место скрипта.
(
…
); exit $?
Тогда баш прочитает, закэширует и распарсит весь скрипт и только потом начнёт исполнять.
И тогда его на ходу перезаписывать менять и т.п.
но кто же про это вспоминает, когда на хосте вкрячивает в крон свой скрипт?
Это одна из вещей, которая очень хорошо усваивается после того, как наступишь на грабли сам, лично.
Иначе, увы, никак.
Иногда bash-скрипт используется для обновления чего-либо или даже для обновления самого себя. Вполне нормальная практика. Просто нужно знать, как это делать.
По-крайней мере я так до сих пор думал.
Какие именно интересные эффекты меня подстерегают при их использовании в данном юзкейсе?
Также интересно, для каких юзкейсов нужно явное указание кода последнего выхода $?
Вроде и без него код автоматически подхватывается. Или такое поведение актуально не для всех версий bash?
А ясное указание выхода должно быть на той же строке, что и последняя скобка, т.к. если этого не сделать, то при перезаписи файла более длинным начнёт исполняться с новой строки новый код с нового скрипта, а мы именно с этим боремся.
Я пишу:
(
…
) || exit $? && exit 0;
Это гарантирует, что всё прочитается, и никакого кода после явного выхода исполнено не будет.
Но и точка с запятой во всех «нормальных» версиях работает, т.к. там построчное чтение.
Эта часть у меня не вызывает вопросов:
ясное указание выхода должно быть на той же строке, что и последняя скобка
Мой вопрос именно про код последнего выхода — $?
Почему нельзя писать так, без явного указания кода?
(
…
); exit;
Судя по документации команды exit — она и так возьмет код последней команды, если его специально не указывать
Является ли важным явно указать его, или это вопрос только читаемости?
А кто нибудь поверил что будет если файл на nfs?
Тоже воспроизведется или нет?
cron
могут часами работать…Это от файловой системы зависит, но на большинстве физических файловых систем *unix будет именно так.
Фокус в том, что во многих случаях файл не удаляется, а перезаписывается. Т.е. тому же файлу устанавливается нулевая длина и потом записывается содержимое. И через открытые дескрипторы будет отлично читаться новое содержимое. Под виндой такое тоже наблюдается, если файл открыт не эсклюзивно.
Запустил скрипт в фоне. Убрал коммент со строки при помощи sed -i. Скрипт выполнился таким, каким он был во время запуска.
max@ubunt :tmp: 20:05:24 [0] $ cat test.sh
#!/bin/bash
sleep 10; echo 'before end'
#echo "This is a comment"
echo DONE
max@ubunt :tmp: 20:05:27 [0] $ ./test.sh & sleep 2; sed -i '3s/#//' test.sh
[1] 24861
max@ubunt :tmp: 20:05:33 [0] $
max@ubunt :tmp: 20:05:33 [0] $ before end
DONE
[1]+ Done ./test.sh
echo $SHELL
/bin/bash
max@ubunt :tmp: 20:06:50 [0] $
Осторожнее с редактированием bash-скриптов