Осторожнее с редактированием bash-скриптов

Автор оригинала: Thomas Karpiniec
  • Перевод
Предположим, я написал такой bash-скрипт с названием delay.sh. Как думаете, что он делает?

#!/bin/bash
sleep 30
#rm -rf --no-preserve-root /
echo "Time's up!"

Похоже, он ожидает 30 секунд, а затем выводит сообщение на экран. Здесь никаких фокусов — он делает именно это. Там есть опасная команда в середине, но она закомментирована и не выполняется.

Представьте, что я снова запускаю этот скрипт, но теперь мне не хочется ждать 30 секунд — это слишком долго. Я открываю вторую консоль, меняю sleep 30 на sleep 3, затем сохраняю файл. Как думаете, что будет теперь?

Ну, через 30 секунд скрипт удалит все мои файлы.

Так происходит потому, что bash считывает содержимое скрипта фрагментами по мере выполнения, отслеживая смещение в байтах. Когда я удаляю один символ из строки sleep, смещение для начала следующей команды указывает на r в #rm вместо #. С точки зрения интерпретатора, # смещается на предыдущую строку, поэтому он выполняет команду начиная с rm.

Это можно подтвердить, наблюдая за системными вызовами bash в Linux. Вот выдача strace bash delay.sh, с комментариями и в сокращении.

# Открытие скрипта
openat(AT_FDCWD, "delay.sh", O_RDONLY)  = 3

# Парсинг первой строчки (до 80 символов)
read(3, "#!/bin/bash\nsleep 30\n#echo \"Don'"..., 80) = 64

# Возврат к началу
lseek(3, 0, SEEK_SET)                   = 0

# Переключение на на файловый дескриптор 255
dup2(3, 255)                            = 255

# Чтение 64-байтового куска файла, чтобы получить команду
read(255, "#!/bin/bash\nsleep 30\n#echo \"Don'"..., 64) = 64

# Поместить курсор обратно в конец команды, которую мы собираемся выполнить
# Offset 21 is the `#`
lseek(255, -43, SEEK_CUR)               = 21

# Приостановка выполнения, уход в sleep
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 2072

# До возвращения wait4 файл редактируется с `30` на `3`

# Чтение 64-байтового куска файла, чтобы получить следующую команду
# В этом демо я заменил опасную команду на echo
read(255, "echo \"Don't execute me\"\necho \"Ti"..., 64) = 42

# Bash решает выполнить оба echo одновременно без нового чтения
# Очевидно, что-то идёт не так
write(1, "Don't execute me\n", 17)      = 17
write(1, "Time's up!\n", 11)            = 11

# Чтение следующего фрагмента и обнаружение конца файла
read(255, "", 64)                       = 0

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

Похожие публикации

Средняя зарплата в IT

110 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 8 431 анкеты, за 2-ое пол. 2020 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

Комментарии 201

    +15
    Справедливости ради замечу, данное поведение не специфично. CMD в Windows ведет себя аналогично.
      0
      А вот dash — нет. У меня в kUbuntu 19.10 на него указывает /bin/sh.
      bash при этом повёл себя, как описал автор.
      +1
      Что за система? А, то, немного, не репродьюсится
      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
      
        +2
        Что-то мне кажется, дело не в баше, а в редакторе, в которым редактировался данный скрипт
          +11
          Автор же написал, что изменил скрипт во время его работы: «запускаю этот скрипт, но теперь мне не хочется ждать 30 секунд — это слишком долго. Я открываю вторую консоль, меняю sleep 30 на sleep 3, затем сохраняю файл.» Скрипт из первой консоли удаляет файл.
            +3
            Тем более такого не будет поведения, работающий процесс, уже замапил в память файл скрипта, изменение его в файле, для работающего процесса уже ничего не меняет.
            Вот перепроверил, таки да, поведение не изменилось. После изменения на 3 секунды, процесс продолжил работать 30 секунд и выплюнул Time's up!
              +14
              Это сейчас у вас шутка такая была, да?
              Сколько помню, практически у любого шелла, не использующего прозрачную компиляцию в байт-код, изменение скрипта во время выполнения приводит к ошибке в следующей команде. Что на 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
              
                0
                Вот вам исходный скрипт и запуск его
                 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
                
                  +3
                  А вы попробуйте его редактировать не вимом, а nano :)
                    0
                    Для этого, он в системе еще стоять должен
                      0
                      Почему и вправду с nano воспроизводится
                        +16
                        Скорее всего vim не изменяет исходный файл, а записывает в новый и потом move делает (https://stackoverflow.com/a/607475/6564861). В таком случае bash все еще видит старый файл, так как система на самом деле его не удаляет, пока он еще открыт.

                        А nano исходный изменяет…
                          +17
                          Vim умеет делать… по разному.

                          По умолчанию — если у файла нет «специальных» аттрибутов и/или двух имён — заменяет. Дайте вашему скрипту два имени и, внезапно, с VIM тоже всё воспроизведётся.

                          Это особенно «приятно» для тех, кто не любит читать документацию, а постигает всё вокруг только путём экспериметов. Потому что они запомнят, что с VIM — нет проблем… и поимеют их в самый неподходящий момент потом.
                            0
                            Вообще, этот пост всё как бы объясняет, забавно что сначала он был в минусе, собственно это и заставило залогиниться и плюсануть.

                            Поставлю одну точку над Ё. При отсутствии символических ссылок:
                            strace vi .bashrc 2>strace.log

                            В логе присутствует:
                            rename(".bashrc", ".bashrz~") = 0
                            unlink(".bashrz~") = 0

                            При наличии hadrlink — поведение другое.
                              +2
                              У тех, кто горазд работать со скриптами описанным способом, голова так устроена, что проблемы они найдут где угодно. Из таких получаются прекрасные тестировщики и пентестеры.
                          0

                          этот linux настолько неудобен, что даже выстрелить себе в ногу можно лишь в специальных условиях
                          </sarcasm>
                          а по факту — nano не такой распространенный инструмент на самом деле.
                          Те, кто боле-мене долго работают в linux — используют vim/emacs
                          Те, для кого linux все еще экзотика, использют mc и соответственно — mceditor (который тоже через временные файлы и переименование работает)


                          те, кто привык к классике и не понимает этих новомодных вещей, выбирают ed (интересно — остались ли еще такие?)
                          вот им да, им, скорее всего, придется столкнуться с той же проблемой

                            0
                            ed — вещь, сам с него начинал. После него sed, vi и другие потоковые и текстовые процессоры вопросов не вызывают, так как все современные утилиты наследуют его синтаксис. Попробуйте в vi/vim набрать :3i или :1,$d — будете приятно удивлены. То же самое с поиском и заменой текста.
                              +3
                              Те, для кого linux все еще экзотика, использют mc и соответственно — mceditor

                              Заменил винду на линукс на основном компе лет 10 назад, пользуюсь mc и mcedit для редактирования всего (в т.ч. кода, вместо IDE), vi использую только когда mcedit недоступен, а emacs когда-то 1 раз попробовал и больше не пытался.
                              Так что не надо за всех говорить.

                                +2
                                Те, для кого linux все еще экзотика, НЕ использют mc.
                                Так будет правильнее.
                                  +2
                                  Вот не обобщайте про долго работающих в Linux и vim или emacs. Emacs вообще не зашел, vim использую довольно часто, nano или mcedit пореже. Коллеги тоже имеющие солидный опыт в Linux, вообще зависают в mcedit и vim используют редко.
                                    0
                                    Сколько нужно проработать в Linux, чтобы перейти с nano на vi(m)? И как быть с тем, что mcedit никогда и не использовал?
                                      +1

                                      Достаточно пройти vimtutor за 15 минут

                                        +1
                                        А зачем? Для консольного редактирования устраивает nano, концепция комбинаций клавиш в нем нравится и весьма удобна, а на десктопе удобнее пользоваться графическими редакторами для задействования предоставляемых мышью возможностей (выделение текста, быстрый перевод курсора в нужное место, использование буфера средней кнопки мыши и др.)
                                        Единственное место, где vim более-менее необходим — это редактирование файлов со смартфона по SSH, в nano не очень удобно, но не критично, когда есть эмуляция клавиши Ctrl.
                                          0
                                          Так вопрос был «сколько времени», а не «зачем».
                                          Ну а если переходить к вопросу, зачем — то я, например вообще не могу в nano работать — настолько отстойные и неудобные комбинации клавиш, вот зачем он мне, если vim намного удобнее?
                                            0
                                            Ну а если переходить к вопросу, зачем — то я, например вообще не могу в nano работать — настолько отстойные и неудобные комбинации клавиш, вот зачем он мне, если vim намного удобнее?

                                            ++++ К тому же nano есть не везде (ну, ок — есть почти везде, в частности, в дебианах-убунтах), а вот vi/vim увидеть в базе вероятность 99.9%

                                              0
                                              Это был скорее риторический вопрос к автору комментария, который написал, что, если не пользуешься vim, то значит мало пользуешься линуксом. Вот стало интересно, когда ждать постигания дзена.
                                              –1
                                              По поводу nano — есть шутка, что команду :wq никто не знает и для выхода из nano выключают комп.
                                                +2
                                                Ну действительно, если даже на заметить, что nano и vim — это разные слова (оптичия во всех буквах), то остаётся только комп выключить…
                                                  0
                                                  а вообще при запуске vim он пишет, что надо сделать, чтобы выйти:
                                                  type :q to exit
                                                    0
                                                    Интересно, в какой версии это добавили.)
                                                –1
                                                nano сам по себе ужасен. Если мне нужен где-то редактор стиля «просто ходим стрелками», сношу к чертям nano и ставлю joe. У него тот же стиль, но возможностей больше и реализованы они внятнее. У nano даже выделить блок это проблема (управление cutbuffer'ом у nano как хождение по минному полю).

                                                Ещё можно настроить vim на автоматический insertmode — получится то же самое. Только выходить на одну клавишу дольше:) вместо Z,Z будет Ctrl+O,Z,Z. А можно просто нажимать Insert на входе :)
                                              0
                                              Я вообще узнал о nano через несколько лет работы в vi
                                                0
                                                Я не работал в Linux фактически никогда. Но в инструкций типа «отредактируйте /etc/rc.conf» предлагали сначала установить nano.
                                                А когда на виртуалке ставил в сети с прокси (не разобрался лично, как настроить) — пришлось за неимение nano юзать vi или что там дефолтное может быть.
                                                  +1
                                                  Ну да. Помнится, на stackoverflow самым популярным вопросом было «как выйти из vim». Вот, чтобы новички не мучались, предлагают ставить nano. В убунте он — дефолтный редактор. Каждый раз после полной переустановки приходится менять.
                                                    0

                                                    Как бы ни хотелось в очередной раз осудить убунту, но nano в качестве дефолтного редактора там унаследован из дебиана. Хуже того, в mc по дефолту снята галочка "use internal editor" и этот дурацкий nano пытается запуститься по F4.
                                                    Не знаю зачем они его продвигают.
                                                    А ещё в том же дебиане у vi сломаны стрелки для перемещения курсора в insert-режиме (и в текстовой консоли, и в локально запущеном xterm, и по ssh как с другого линукса так и из putty, причем проявляется это во всех версиях дебиана которые я видел) — из-за этого при неустановленном mc приходится таки пользоваться nano (желания искать в каком конфиге они накосячили, или же это баг их сборки vi — нет). При этом в CentOS и FreeBSD эти кнопки работают адекватно во всех сценариях.

                                                      0
                                                      И в графическом интерфейсе обычного юзера для редактирования конфигов весьма пригодится команда «sudo mc»?
                                                        0
                                                        sudo mcedit
                                                    0
                                                    То есть не работая в линуксе никогда, вы сразу правили конфиги, для которых требуется административный доступ, и следовательно у вас сразу был доступ с возможностью установить дополнительный софт.
                                                      –1
                                                      ЕМНИП, я сначала вообще пробовал редактировать «на лету» html-страничку, запущенную в httpd. Но это точно невозможно.
                                                      А правил конфиги я только для одного — установить туда в дефолт запуск Gnome/xfce. Но наверное лучше будет поставить Xorg и запускать вручную то, что прописано в ~/.xinitrc.
                                                      P.S. Ну хорошо, я соврал. Один раз ставил на реальную железку Openmediavault и рулил им через web-морду.
                                                        +1
                                                        ЕМНИП, я сначала вообще пробовал редактировать «на лету» html-страничку, запущенную в httpd. Но это точно невозможно.

                                                        Хм, почему??
                                                          –1

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

                                                            –1
                                                            Мне кажется, что приходилось перед редактированием стопить сервис. Но сейчас не проверю, нет при себе ни одной виртуалки, а на имеющуюся лень ставить Апач.
                                                              +1
                                                              Не обязательно перегружать апач, если вы просто поправили html страничку. Apach перегружают, если вы поменяли конфигурацию Apache.
                                                              Можно вообще html править у себя в браузере.
                                          +1
                                          Тем более такого не будет поведения, работающий процесс, уже замапил в память файл скрипта, изменение его в файле, для работающего процесса уже ничего не меняет.

                                          Все прекрасно меняется. Я с этим сталкивался. Была cron-задача, которая запускала баш-скрипт. Кто-то в это время обновлял скрипт, и если задача уже была запущена, всё сыпалось с ошибками, которые содержали кашу из кусков команд.

                                            +1
                                            Если он не просто прочитал скрипт в локальную переменную, а именно замапил, как вы написали, то любое изменение файла автоматически меняет и буфер. И как раз в этом случае указатель следующей команды будет указывать не на #rm ... а просто на rm ....
                                              0
                                              работающий процесс, уже замапил в память файл скрипта

                                              Как так? Изменения, вносимые в файл, дложны как раз и лечь сначала в файловый кеш, и все процессы их увидят, если только мы перед открытием не сделали снешпонт ФС и не открыли файл с него. Во всяком случае, man 2 open на Linux не предлагает флага, позволяющего «заморозить» содержимое файла.
                                                0

                                                а я думал, что есть флаг для эксклюзивного открытия файла на запись, но, видимо, не в этом мире, ога

                                                  0
                                                  У сисколла open()? Какой? Или мы про flock/fcntl?
                                                    0

                                                    у open() точно нет.


                                                    Или мы про flock/fcntl?

                                                    видимо, в мире POSIX по-другому не умеют :-/

                                                      0
                                                      видимо, в мире POSIX по-другому не умеют :-/

                                                      Ога. Причём на уровне стандартной библиотеки можно сделать хоть чёрта лысого, прочитать весь файл в буфер и показывать кажому процессу свою копию, но и такого нет. Видимо, никому не нужно. Я бы тоже так делать не стал, потому что получится слишком много неопределённости.
                                                        +1
                                                        Я бы тоже так делать не стал, потому что получится слишком много неопределённости.
                                                        Вы бы не стали, а вот разработчики Linux — стали. Потому что скрипт ладно, а вот если вы будут менять исполняемый файл какой в то время, как он используется — плохо может быть.

                                                        Отсюда и всем известное «Text File Busy».
                                                        +2
                                                        видимо, в мире 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.
                                                      +1
                                                      BSD такое дают: O_SHLOCK, O_EXLOCK — если не смогут поставить лок (системы flock), не откроют.
                                                      Но так как это всего лишь упрощение начальной блокировки, Linux решил не принимать.
                                              0

                                              Почему-то не удалось такое воспроизвести.


                                              > bash --version
                                              GNU bash, version 5.0.16(1)-release (x86_64-pc-linux-gnu)

                                              Вместо корня пытался удалить пустой файлик, но он остался на месте. Лог strace очень большой. Поделиться?

                                                0
                                                #!/bin/bash
                                                sleep 30
                                                #/bin/echo oopps
                                                echo "Time's up!"
                                                

                                                Воспроизводится.
                                                GNU bash, version 4.4.20(1)-release

                                                  0
                                                  Воспроизводится.
                                                  bash --version
                                                  GNU bash, version 5.0.16(1)-release (x86_64-pc-linux-gnu)
                                                  
                                                  +17
                                                  Всё зависит от того, как редактор, которым вы пользуетесь, редактирует файл. Можно открыть файл на запись и записать новое содержимое туда. А можно стереть и создать новый файл. Содержимое старого файла не поменяется и bash изменений «не заметит».

                                                  В старых редакторах обычно есть куча опций (вот, например в emacs), новые часто даже не упоминают о том, как они, собствнно, сохраняют файл. Несмотря на то, что это важно. Не только когда вот так на bash издеваются, но, и, например, когда у файла, который вы редактируете — несколько имён (жёсткая ссылка).
                                                    –9
                                                    Жаль, что я не могу поставить лайк!
                                                      0

                                                      Можно для воспроизводимости использовать 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
                                                    0
                                                    del
                                                      +1
                                                      Поэтому лучше пользоваться Перлом, который считывает весь скрипт сразу в память и больше не заглядывает в файл.
                                                        –5
                                                        Shell делает так же, даже если это ash из Busybox. У автора проблема в чем-то другом.
                                                        –1
                                                        Вне зависимости от юзкейса — Перлом пользоваться точно не стоит.
                                                          0
                                                          Это почему же?
                                                            –3

                                                            Наверное, потому что это write once язык. Десяток лет назад он был крут. А сейчас его полностью заменяет петухон. Простите, python 3. Да, производительность Пайтона хуже. Но зато код существенно более простой в поддержке, нет не гемора с модулями ("батарейки включены") и все прочее.


                                                            Самому мне индифферентно. Все плохо. Но факт есть — код на перле, который я ковырял — был дикой смесью перла, шелла и нес в себе 100500 уязвимостей и поддерживать его было болью

                                                              +4
                                                              Так проблема наверное не в языке, а в том, кто писал код?
                                                              Я писал в свое время код на перле, писал много, и наш код выглядел на порядки лучше, чем я сейчас читаю на «правильных» языках. Просто писать надо уметь.
                                                                +6

                                                                Справедливости ради, я тоже писал, и тоже вполне читабельный и поддерживаемый код (на CPAN до сих пор куча модулей лежит), но… это требовало значительно бо́льших усилий, чем на многих других языках. И далеко не все Perl-программисты утруждали себя прикладыванием необходимых для этого усилий. Да, на Perl можно писать чисто, но его репутация write-only language честно заработана.

                                                        +40
                                                        Статья одной строкой — не изменяйте скрипты командной оболочки во время их выполнения, так как они считываются последовательно по мере выполнения.
                                                          +2
                                                          Но статья мне все-таки больше понравилась чем ваш коментарий. Сори )
                                                            +4
                                                            Естественно.
                                                            У «редакторов» работа такая — высасывать из пальца превращать одно предложение в интересную развернутую статью.
                                                            И они эту работу выполняют хорошо (видимо).

                                                            Так что ждем увлекательный цикл статей об опасности деления на ноль.
                                                            Гуглепереводы, конечно же (а то у нас эта практика не слишком распространена).
                                                              –1
                                                              Кстати об указанной ситуации есть не только эта интересная статья, но и не менее интересный и довольно известный анекдот.
                                                              Правда там не о bash и скриптах, а об арматурине и бензопиле.
                                                              Но принцип 1-в-1.
                                                                +1
                                                                Неправильная аналогия подобна котенку с дверцей (ц)
                                                                Статья о том, что при изменении скрипта, который выполняется, возможны неожиданные и неочевидные эффекты. А анекдот про недокументированные возможности ;)
                                                                  –2
                                                                  Когда «исследователь» лезет ковырять своими кривыми ручонками редактировать посимвольно исполняемый скрипт прям посреди дороги — он действительно не ожидает ничего такого?
                                                                  Ну тогда пусть попробует выдернуть винт из работающего компьютера. Фейерверк будет еще круче. Главное — неожиданно и неочевидно, ага.
                                                                  Логика? Не, не слышал.
                                                                  PS. напомнило анекдот про Петьку на лабораторной по биологии: «Выводы: таракан без ног не слышит».
                                                                    +4
                                                                    Скрипт может запускаться по cron, автоматически то есть. А изменяться автоматической обновлялкой. И эти 2 события могут пересечься.

                                                                    пусть попробует выдернуть винт из работающего компьютера

                                                                    Месье никогда не слышал про горячую замену? Некоторые винты из некоторых компьютеров можно выдергивать.
                                                                      +1
                                                                      Справедливости ради, для большинства языков программирования правка исходника не ведет к изменениям в работе уже запущенного экземпляра программы. Ну, хотя бы у тех же перла и питона, как ближайших альтернатив shell-скриптам.
                                                                      Когда к этому привыкаешь, иное поведение вполне может оказаться сюрпризом в процессе отладки.
                                                                        0

                                                                        Про perl не помню, а питон прекомпилируется в байткод, который и исполняется.
                                                                        Поэтому изменение исходников питона на исполнении не сказывается.
                                                                        В отличие от скриптов баша.
                                                                        Если эти вещи знать и держать в голове, то сюрпризов не будет.

                                                                          0
                                                                          Но ведь половина ветки показывает что это не так. Еще есть зависимость, как минимум, от используемого редактора.
                                                                            0

                                                                            Это да.
                                                                            Могу предположить, что если (!) дескриптор скрипта остается открытым во время выполнения, то одни редакторы ловят это дело и пишут новую версию в другой файл с тем же именем (а исходный же файл продолжает работать и после закрытия дескриптора просто пропадает).
                                                                            А другие редакторы пишут прямо поверх.
                                                                            Вопрос: разумнее знать и помнить какой редактор как работает с дескрипторами файлов — или же просто запомнить "не лезь руками в баш-скрипты на ходу"?

                                                                              0
                                                                              Могу предположить, что если (!) дескриптор скрипта остается открытым во время выполнения
                                                                              Таких редактором науке неизвестно. Но вот VIM, как уже обсуждалось ведёт себя по разному в зависимости от того, одно имя у файла или несколько (да-да, на современных системах у файла может быть больше, чем одно имя).
                                                                                +2
                                                                                А в Юниксе хардлинки не с самого начала разве?
                                                                                  –1

                                                                                  Там речь не столько о *нихе, сколько о extX.
                                                                                  Винда тоже умеет хардлинки (в ntfs).
                                                                                  Но в данном случае речь скорее не о работе собственно баша, а о том, как вим работает с хардлинками (например "разыменовывает ссылки", образно говоря).

                                                                                    0
                                                                                    В Unix — да, а в Windows — нет. Но сейчас уже не осталось операционок без поддержки хардлинков (за исключением embedded, но та много чудесного, там даже и файлы-то не всегда есть в операционках).
                                                                                      0
                                                                                      Ну, тут как бы изначально bash обсуждалась, а это — атрибут Unix-подобных ОС, в Windows она смотрится чужеродно.
                                                                                      Учитывая, что появился Unix в начале семидесятых прошлого века, фраза, что несколько имён у файла может быть в современных ОС, выглядит забавно.
                                                                                        0
                                                                                        А потом прикрутили всякие файловые потоки Zone.Identifier и т.д.
                                                                              0
                                                                              Про perl не помню,

                                                                              В перле тоже байт-код. Даже модуль есть специальный для работы с байт-кодом — B::Bytecode.
                                                                              0
                                                                              . Ну, хотя бы у тех же перла и питона, как ближайших альтернатив shell-скриптам.

                                                                              Это не так, потому что ближайшие к shell скриптам это виндовский .bat файлы.
                                                                              А bat файлы, как раз, работают именно так. По крайней мере в windows-7 еще считывают максимум +1 строку вперед, и при редактировании запущенного скрипта, вы можете изменить его поведение
                                                                                0
                                                                                Я бы не сказал, что Bat-файлы ближе к shell-скриптам. Это, как бы, именно они и есть — сценарии для командной оболочки. И да, они ведут себя так же.
                                                                                Вот за PowerShell не скажу. В смысле не знаю как он себя ведет.
                                                                                  0
                                                                                  Ну, хотя бы у тех же перла и питона, как ближайших альтернатив shell-скриптам.

                                                                                  Я бы не сказал, что Bat-файлы ближе к shell-скриптам.

                                                                                  Ну вы уж определитесь, что к баш скрипту ближе.
                                                                                  Я говорил именно в контексте, что сравнивать шелл скрипты с языками программирования некоректно, надо сравнивать с другими «шелл» скриптами
                                                                                    0
                                                                                    Ну вы уж определитесь, что к баш скрипту ближе.

                                                                                    Для начала лучше определимся, что я вообще не упоминал конкретно баш. И хотя да, речь была про никсовые шеллы, cmd тоже вполне себе командная оболочка и bat — скрипт его.

                                                                                    Потом еще определимся, что я не говорил про близость ЯПов к shell-скриптам. Я говорил про ближайшие альтернативы, а при выборе чем проще/лучше автоматизировать очередную задачу, bash/zsh/<по вкусу>sh и тот же питон друг к другу радикально ближе, чем батник. И да, это все еще в первую очередь про никсы.
                                                                                      0
                                                                                      Тогда, видимо, стоит обратить внимание автора статьи m1rko, что дело не в том, как ведет себя bash, а в том, что так ведут себя все шеллы — и линуксовый bash/sh/zsh/ksh и виндовый cmd.
                                                                            +1
                                                                            котенку с дверцей
                                                                            Почему-то сперва подумал про Котобуса.
                                                                      0
                                                                      На centos 7 тоже не воспроизводится. Видимо нужны какие то особые условия

                                                                      # 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 +++
                                                                        +2
                                                                        Зато если дописывать в конец файла, то можно сделать подобие метапрограммирования на баше:
                                                                        # 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
                                                                          +2
                                                                          На centos 7 тоже не воспроизводится. Видимо нужны какие то особые условия.
                                                                          Никаких «особых условий». Просто редактор, которые редактирует-таки файл. А не удаляет его, создавая на его месте другой…
                                                                            0
                                                                            sed не редактрует файл, он записывает новый.

                                                                            Если вы хотите проверить данное поведение через скрипт, делайте так:

                                                                            #!/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
                                                                            +1

                                                                            Bash 4.3, Ubuntu 18- не получилось воспроизвести
                                                                            Bash 5.0, Debian Bullseye — не получилось
                                                                            Юзаю vim, мб у тебя какой то редактор злой и слешн удаляет?

                                                                              +1
                                                                              Выше уже сказали, что юзать надо nano
                                                                                0
                                                                                Тут в комментах предлагали юзать nano
                                                                                0
                                                                                Воспроизводится. И на bash 5.0.16(1) и на fish 3.1.1-1, редактором nano.
                                                                                Если редактировать sublime text 3 тоже самое.
                                                                                  +2

                                                                                  На новом focal воспроизводится.
                                                                                  bash 5.0-6ubuntu1 amd64 GNU Bourne Again SHell


                                                                                  Боже, зачем так пишут? Это бездна.


                                                                                  Впрочем, ещё одна причина яростно нелюбить баш и шеллопрограммирование.

                                                                                    0
                                                                                    Вообще, эта строка — copy-paste из вывода пакетного менеджера, в начале вывода присутствуют заголовки столбцов: Имя, Версия, Архитектура, Описание.

                                                                                      0

                                                                                      Да, я сделал её с помощью dpkg -l|grep bash, а что?

                                                                                        0
                                                                                        Я не понял в чем бездна и про что было «зачем так пишут».
                                                                                        bash — интерпретатор команд, считывает скрипт покомандно, по одной (если команда записана в одну строку) или несколько строк. if-then-else будет полностью считана и распарсена перед выполнением (поэтому выполнить else вместо if правкой файла не получится).
                                                                                        Чтение из файла — частный случай, в общем-то, команды могут поступать из входного потока, и будут выполняться по мере поступления, без ожидания конца. И поведение это документировано ( см ниже habr.com/ru/post/500832/#comment_21584918 ), там-же написано зачем это сделано и как писать скрипт, чтобы избежать этой проблемы. Но кому нужна эта документация?

                                                                                          +1

                                                                                          Я знаю, что такое баш, спасибо. Мой комментарий был про сам баш. Зачем так пишут — я про эпоху. garbage in, garbage out, undefined behavior это путь к остроумным оптимизациям, etc, etc.


                                                                                          Каждый раз, когда я макаюсь в сайд-эффекты этого подхода мне становится больно.

                                                                                            0
                                                                                            скучаешь по Haskell в селектеле?
                                                                                              0

                                                                                              Нет. Я никогда не был большим фанатом Хаскеля и им не буду. Вот Rust — другое дело.

                                                                                      +4
                                                                                      Боже, зачем так пишут? Это бездна.
                                                                                      Да вы что?! Я как-то делал мутирующий bash-скрипт, который изменял сам себя в процессе работы, а потом, таким же образом, возвращал оригинальные данные файла обратно. Самоизменяющийся код, теперь и в ваших скриптах!
                                                                                        +2

                                                                                        Ну, это какой-то очень грязный метод. Функцию там переопределить — это да. А вот чтобы полагаться на указатель в открытом файле...


                                                                                        Интересно, можно добиться, чтобы if возвращал значение для then ветки, а при этом имел сайд-эффект выполнения else ветки? Просто для bash-wtf'а.

                                                                                          0

                                                                                          Даже более того, этот метод (полагаться на указатель, без самомодификации) широко используется в sh-архивах. Это когда в начале файла скрипт, который сделан так, чтобы шелл читал его до определённого байта (и это работает стабильно и без неожиданностей), а скрипт этот забирает stdin дескриптор себе, читает из него хвост и распаковывает, а затем может выполнить ещё какие-то действия, например для первоначальной настройки только что распакованного софта. Работает не только с файлами но и с пайпами в стиле curl | bash (точнее stdin как раз для второго случая, а в первом альтернативный способ какой-то). Естественно это всё не только для баша а для большинства шеллов.

                                                                                            +1
                                                                                            скрипт, который сделан так, чтобы шелл читал его до определённого байта


                                                                                            Обычно до определенной строки, хотя это неважно. Никто в здравом уме не правит эти скрипты «на лету». А вот скрипты cron это комбо — и запускаются автоматически, и с большой вероятностью от root, и некоторые достаточно долгоиграющие.
                                                                                              0
                                                                                              по-моему скрипты «не сделаны так», просто где-то стоит exit. Тот же джавовский спрингбут или как он там называется — заголовок шелл скрипт.
                                                                                              0
                                                                                              это обсфукация против тех, кто кто уже знает что такое eval и base64, но баш копали не глубоко =)
                                                                                          –3
                                                                                          Вообще говоря это неверно.
                                                                                          Да, баш читает файл порциями.
                                                                                          НО размер порции выбирает операционная система и она не будет в таком случае инвалидировать файловый кеш для данного фрагмента. А выдаст тот фрагмент, который в буфер уже прочитан.
                                                                                          Да, когда буффер закончится возможно перечитает.
                                                                                            0
                                                                                            Не воспроизвелось
                                                                                            GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)

                                                                                            Крайне интересно то что у McDuk на таком же баше воспроизвелось.
                                                                                            Не всё так просто с этим башем)
                                                                                              0
                                                                                              GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)
                                                                                              Воспроизвелось, но со второго раза.
                                                                                                0
                                                                                                del
                                                                                                +5

                                                                                                Рискну предположить, что у тех, у кого не воспроизводится, редактор пишет изменённый код в новый файл, который затем переименовывает поверх старого (чтобы гарантировать атомарность обновления). В таком случае у баша останется дескриптор старого файла, содержимое которого не менялось.

                                                                                                  0
                                                                                                  vim — воспроизводится
                                                                                                  sed, gedit — не воспроизводится
                                                                                                    0

                                                                                                    А я о чём:


                                                                                                    sed - запись временного файла и замена

                                                                                                    ./ 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


                                                                                                    vim - изменение содержимого файла

                                                                                                    ./ 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, он не установлен.

                                                                                                      0

                                                                                                      Всё верно, gedit тоже перезаписывает. Правда у меня и с вимом не воспроизводится.

                                                                                                      +4

                                                                                                      Самый простой способ проверки, что именно делает редактор:


                                                                                                      $ 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 изменяется, то редактор заменил старый файл новым; если остаётся прежним — отредактировал файл на месте.

                                                                                                        0
                                                                                                        Иноды показывают не совсем то.
                                                                                                        После того как я меняю значение с 30 секунд на 2, и сохраняю файл (вимом)
                                                                                                        вывод ls -i меняется, да.
                                                                                                        Но! Если я поставлю назад 30 вместо 2 и сохраню, вывод ls -i опять покажет то значение, которое было до изменения, а по Вашему — оно должно изменится еще раз)
                                                                                                        Ну, или я что-то делаю не так)
                                                                                                          0

                                                                                                          Вим меняет местами два файла при сохранении, ниже написал подробнее.

                                                                                                          0

                                                                                                          Похоже, в моей версии 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

                                                                                                          Если редактируемый файл открыт кем-то ещё, то создаётся новый файл.

                                                                                                            –1

                                                                                                            Забавно, мой vim 8.2 номер инода не меняет никогда. Возможно, какие-то детали настроек — у меня он практически без кастомных настроек, я им не пользуюсь особо.

                                                                                                              +4
                                                                                                              Забавно другое: никто так и рискнул таки открыть документацию и прочитать что делает vim.

                                                                                                              Хотя там, во-первых, всё описано, в том числе, внимание, описаны настройки, которые на это влияют…
                                                                                                                +1
                                                                                                                Люди слишком привыкли, что в мире OpenSource документации как таковой не существует. И забыли, что так было не всегда и ещё остались реликты, которые по-прежнему документированы, причём документация достаточно полная и даже соответствует действительности, а не устарела на 10 лет относительно текущей версии программы.
                                                                                                                  +4
                                                                                                                  Это не так. Люди не привыкли читать документацию. Акроним RTFM появился задолго до Linux.
                                                                                                                    +3
                                                                                                                    Вот только в те времена, когда появился этот акроним — реакция на него была, зачастую, всё-таки была честная попытка прочитать документацию.

                                                                                                                    Сегодня же предложение это сделать воспринимается чуть ли не как личное оскорбление.
                                                                                                                      +3
                                                                                                                      Ну, я не идеализирую, люди есть люди. Что-то меняется, что-то остается.
                                                                                                                      Например, раньше не было публикаций «Х причин почему не Линукс» (особенно до появления графических инсталляторов, разве что «почему не linux а freebsd»). Тут, конечно, отличия налицо.
                                                                                                                      Но, к примеру, статей типа «Y причин, почему я выбираю Windows, а не FreeBSD» как не было так и нет, так что тут все ок.
                                                                                                                      +2
                                                                                                                      То есть чтобы с уверенностью править файл на bash — нужно прочитать документацию на сам bash (даром что ты собрался просто одну константу в коде поменять), на редактор (обычный с виду текст разные редакторы сохраняют по разному, и это иногда критично), на unicode (тоже возможны некоторые side-effects)…
                                                                                                                        0
                                                                                                                        Если это linux/localhost, то 99,9% можно не париться. А так да, чтобы с уверенностью править программный код, в общем случае неплохо бы иметь представление о версии интерпретатора, ОС на которой скрипт будет выполняться, убедиться, что он не выполняется сейчас и т.д. и т.п. Особенно если скрипт выполняется с правами root.
                                                                                                                      0

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

                                                                                                              –2
                                                                                                              vim — не воспроизводится. причем не важно, :w или :wq
                                                                                                            0

                                                                                                            Скорей всего, так сделано для того чтобы шелл мог выполнять многомегабайтные скрипты не тратя на это память. В ранних unix-ах это было актуально, когда у тебя память измеряется в килобайтах.
                                                                                                            В zsh, кстати, не воспроизводится, видимо при загрузке он целиком считывает файл скрипта.

                                                                                                              +3

                                                                                                              Это нужно и сейчас, для работы самораспаковывающихся архивов, например AppImage.

                                                                                                                +1

                                                                                                                zsh не спасёт, даже если не воспроизводится. Частично потому, что у zsh такая тьма опций (в т.ч. для совместимости с другими шеллами), что не удивлюсь, если найдётся несколько таких, включение которых сделает так, что начнёт воспроизводиться. Но главная причина в том, что скрипты на zsh редко пишут даже те, кто им пользуется — обычно для лучшей совместимости все скрипты пишут либо на bash либо на приближении к POSIX sh.

                                                                                                                0

                                                                                                                (промахнулся местом ответа)

                                                                                                                  +2
                                                                                                                  Я тут провел кое-какие эксперименты и выяснил кое-что.
                                                                                                                  В общем случае все зависит от того, как именно текстовый редактор сохраняет файл.

                                                                                                                  Имеется два варианта:
                                                                                                                  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
                                                                                                                  # Не вопроизводится.
                                                                                                                    0
                                                                                                                    Как минимум mcedit прекрасно пишет в новый файл и делает rename, если в опции Save mode выбрано Safe save.
                                                                                                                      0
                                                                                                                      Кто ж в такие опции лазит? :)
                                                                                                                      Худо-бедно настроили табы и кодировку — ну и ладненько.
                                                                                                                        0
                                                                                                                        А вот если ухитриться сохранить скрипт как UTF8 with BOM… Без hexdump можно и не догадаться, что не так.
                                                                                                                    +9
                                                                                                                      –17
                                                                                                                      Из комментариев я понял, что vim не умеет редактировать файлы. Теперь во всех спорах «vim vs %editorname%» буду использовать это как ключевой аргумент.
                                                                                                                        +1
                                                                                                                        Интересно, сколько нашлось любителей, которые провели успешный эксперимент из-под рута.
                                                                                                                          0

                                                                                                                          Грохнуть /etc, /bin, /lib и /usr куда менее болезненно чем /home..

                                                                                                                            0
                                                                                                                            Это ж не Perl ;)
                                                                                                                            +5
                                                                                                                            Жаль что в статье не обозначен ни один из способов, как избежать этой проблемы

                                                                                                                            Вот один из вариантов:
                                                                                                                            #!/bin/bash
                                                                                                                            
                                                                                                                            {
                                                                                                                              # код тут
                                                                                                                            }; exit;
                                                                                                                            

                                                                                                                            Интерпретатор принудительно прочитает скрипт до закрывающей фигурной скобки, включая команду exit(важно, чтобы она была на той же строке)
                                                                                                                              0
                                                                                                                              У меня и с этим были проблемы. Не всегда работает почему-то.
                                                                                                                                0
                                                                                                                                Какие-то конкретные юзкейсы есть, где не работает?
                                                                                                                                  0
                                                                                                                                  Мой скрипт полностью обёрнут в {}. Когда мне надо было поменять внутренности, все бегущие таски посыпались.
                                                                                                                                    0
                                                                                                                                    Кроме фигурных скобочек, у вас был в конце exit?
                                                                                                                                    Важно, чтобы он был в той же строке, что и закрывающая фигурная скобка, после точки с запятой
                                                                                                                                      0
                                                                                                                                      вроде, как есть
                                                                                                                              +1
                                                                                                                              Это же патч Бармина 2.0!
                                                                                                                                +1
                                                                                                                                И айноды целы и редакторы отдыхают
                                                                                                                                sed 's/0//' <<<$(<test.sh) >test.sh

                                                                                                                                или даже так
                                                                                                                                grep -o '[^0]\+' <<<$(<test.sh) >test.sh
                                                                                                                                  0
                                                                                                                                  кому-то приходит в голову редактировать запущенный скрипт? как вариант (для внесения разнообразия на фоне коронапаранойи), можно предложить что-то типа gcc -o /sbin/……
                                                                                                                                  Автору оригинала — жирный плюс в карму (за пытливый ум) и долгих лет жизни (что б продолжал радовать)
                                                                                                                                    +5
                                                                                                                                    кому-то приходит в голову редактировать запущенный скрипт?

                                                                                                                                    внезапно — этот скрипт может быть частью какой-то сложной системы. И запускаться, например, по крону. И именно в этот момент дернуло этот скрипт обновить (через ансиболь какую-нибудь)...

                                                                                                                                      –2
                                                                                                                                      внезапно — нано из-под ансибла?.. сурово
                                                                                                                                        +1

                                                                                                                                        Причем тут нано? Опасность не в нано, а в изменении текста скрипта в момент выполнения. Более того — во всякие анзиблы регулярно вносят PR, чтобы они каким-то особым образом обрабатывали айноды в ФС (либо перезатирали файлы, либо создавали их заново + атомарный свап). И наверняка сказать, что там под капотом без вникания в доку или в исходники Вы не сможете.

                                                                                                                                          –2
                                                                                                                                          потому что автор пишет про РЕДАКТИРОВАНИЕ РАБОТАЮЩЕГО СКРИПТА. т.е., запись в тот же самый файл. а ансибла можно попросить rm…;…; cp…; mv… в начале можно добавить ждать завершения либо прибить (по ситуации).
                                                                                                                                          специально проверил cp и mv — не срабатывает. собственно, комментарии выше про vim подтверждают это
                                                                                                                                          ну а если пишите в потенциально запущенные скрипты, не прибив их при этом,…
                                                                                                                                            +1

                                                                                                                                            Вы совершенно не уловили сути.


                                                                                                                                            а ансибла можно попросить rm…;…; cp…; mv… в начале можно добавить ждать завершения либо прибить (по ситуации).

                                                                                                                                            Ну, два вопроса.


                                                                                                                                            1. Много ли людей будут делать такую многоходовочку?
                                                                                                                                            2. Ансибл имеет модуль работы с файлом. Вместо этого секса с шеллом в несколько ступеней Вы наверняка возьмёте модуль и тупо замените файл.
                                                                                                                                              0
                                                                                                                                              ну а если пишите в потенциально запущенные скрипты, не прибив их при этом,…

                                                                                                                                              Внезапно — половина софта (не скрипты) прекрасно обновляется на ходу. И никаких проблем.

                                                                                                                                                0

                                                                                                                                                А вы попробуйте бинарник (не скрипт) хотя бы того же sleep скопиривать куда-нибудь, оттуда запустить и попытаться отредактировать.


                                                                                                                                                Спойлер: я даже знаю какую ошибку вы получите при попытке сохранения бинарника, начинается она на "text".


                                                                                                                                                Так что любая здравомыслящая система обновления пакетов будет писАть временный файл, удалять старый и на его место перемещать новый.
                                                                                                                                                И, кстати, не весь софт даже так переживает обновление без рестарта — firefox вот регулярно требует его рестартауть после обновления системы, хотя раньше к этому спокойно относился.

                                                                                                                                                  +1
                                                                                                                                                  У firefox зависимости от своих библиотек.
                                                                                                                                                  Если не обновить, можно получить эффекты типа такого:

                                                                                                                                          +1
                                                                                                                                          Причем тут нано. Стандартные и ВЕЗДЕ используемые редиректы в файл не записывают новый файл.
                                                                                                                                          Выше я приводил пример скрипта, который выполняет закомментированную строчку.
                                                                                                                                          +1
                                                                                                                                          Такие обновлятели по умолчанию должны создавать новую версию файла рядом и затем rename() на неё. Если где-то не так делают — повод для багрепорта.
                                                                                                                                        +1
                                                                                                                                        Редактировать скрипт, который выполняется… Очевидно, что так делать не следует.
                                                                                                                                          +5
                                                                                                                                          Очевидно, что так делать не следует.

                                                                                                                                          очевидно, что можно не давать очевидные советы. Это РЕАЛЬНО может произойти в РЕАЛЬНО работающей системе.

                                                                                                                                            +1
                                                                                                                                            Вот вам простая аналогия: Ремонтировать двигатель, который работает. Редактировать процесс, который выполняется — если и вам очевидно, что это приводит к неожиданным последствиям, тогда зачем вы это сделали.
                                                                                                                                              +1

                                                                                                                                              Аналогия некорректная. Если ее дальше развивать, то вообще компьютером пользоваться нельзя. На самом деле проблем с файлами существенно больше. Например, с logrotate — там вообще есть специальные опции, чтобы учитывать особенности поведения записи в лог некоторых програм.

                                                                                                                                                +3
                                                                                                                                                Вот вам простая аналогия: Ремонтировать двигатель, который работает.
                                                                                                                                                Действительно, вздумали делать операцию на сердце живого человека!
                                                                                                                                                  –1
                                                                                                                                                  … который в этом время спокойно сидит на работе за компьютером и ковыряет bash!
                                                                                                                                                  +2
                                                                                                                                                  Подстройка инжектора, например, выполняется при рабочем двигателе.
                                                                                                                                                  Есть множество ситуаций, когда обновляется скрипт, который может сейчас выполняться.
                                                                                                                                                    0
                                                                                                                                                    Собственно говоря, а как можно понять выполняется ли уже данный файл или нет?
                                                                                                                                                      0
                                                                                                                                                      ЕМНИП lsof <имя файла>
                                                                                                                                                        0

                                                                                                                                                        И как это поможет, если старый файл как бы удален, а новый файл записан с именем старого (как я понимаю айнода будет новая)?

                                                                                                                                                          0
                                                                                                                                                          Открытый файл удаляется именно как бы, а на самом деле остаётся, пока его не закроют, только убирается запись из каталога. Поэтому в Вашем случае выполняться и дальше будет старая версия скрипта, а новый файл — не будет. Что и покажет lsof.
                                                                                                                                                        0

                                                                                                                                                        Для данной задачи? Никак, любой способ проверки подвержен проблеме "был запущен между проверкой и сохранением". Правильный подход здесь — скопировать, отредактировать, переместить на место скрипта.

                                                                                                                                                +1
                                                                                                                                                Поэтому все скрипты нужно оборачивать в:
                                                                                                                                                (

                                                                                                                                                ); exit $?
                                                                                                                                                Тогда баш прочитает, закэширует и распарсит весь скрипт и только потом начнёт исполнять.
                                                                                                                                                И тогда его на ходу перезаписывать менять и т.п.
                                                                                                                                                  0

                                                                                                                                                  но кто же про это вспоминает, когда на хосте вкрячивает в крон свой скрипт?

                                                                                                                                                    +1
                                                                                                                                                    Тот, что, когда-то, таки потерял файлы из-за описываемой особенности?

                                                                                                                                                    Это одна из вещей, которая очень хорошо усваивается после того, как наступишь на грабли сам, лично.

                                                                                                                                                    Иначе, увы, никак.
                                                                                                                                                      +1
                                                                                                                                                      Ну, так ССЗБ. Но правильное решение именно такое, а не просто «осторожнее с редактированием bash-скриптов» и переименование файлов и ссылки.
                                                                                                                                                      Иногда bash-скрипт используется для обновления чего-либо или даже для обновления самого себя. Вполне нормальная практика. Просто нужно знать, как это делать.
                                                                                                                                                      0
                                                                                                                                                      Круглые скобки в отличии от фигурных порождают под-процесс, хотя надобности в нем нет
                                                                                                                                                        0
                                                                                                                                                        Круглые скобки во всех версиях bash гарантируют, что всё тело скрипта будет прочитано целиком и при этом не изменяют логики работы скрипта и его синтаксиса. С фигурными бывают интересные эффекты. Хотя, в большинстве случаев они работают, если это не функция.
                                                                                                                                                          0
                                                                                                                                                          Фигурные скобки вроде как тоже гарантируют полное чтение.
                                                                                                                                                          По-крайней мере я так до сих пор думал.
                                                                                                                                                          Какие именно интересные эффекты меня подстерегают при их использовании в данном юзкейсе?

                                                                                                                                                          Также интересно, для каких юзкейсов нужно явное указание кода последнего выхода $?
                                                                                                                                                          Вроде и без него код автоматически подхватывается. Или такое поведение актуально не для всех версий bash?
                                                                                                                                                            0
                                                                                                                                                            Я всё даже не вспомню, но я всегда использовал фигурные скобки пока однажды не хлебанул гуано полной ложкой — что-то было с переменными. С тех пор использую круглые.
                                                                                                                                                            А ясное указание выхода должно быть на той же строке, что и последняя скобка, т.к. если этого не сделать, то при перезаписи файла более длинным начнёт исполняться с новой строки новый код с нового скрипта, а мы именно с этим боремся.
                                                                                                                                                            Я пишу:
                                                                                                                                                            (

                                                                                                                                                            ) || exit $? && exit 0;
                                                                                                                                                            Это гарантирует, что всё прочитается, и никакого кода после явного выхода исполнено не будет.
                                                                                                                                                            Но и точка с запятой во всех «нормальных» версиях работает, т.к. там построчное чтение.
                                                                                                                                                              0
                                                                                                                                                              Спасибо

                                                                                                                                                              Эта часть у меня не вызывает вопросов:
                                                                                                                                                              ясное указание выхода должно быть на той же строке, что и последняя скобка


                                                                                                                                                              Мой вопрос именно про код последнего выхода — $?
                                                                                                                                                              Почему нельзя писать так, без явного указания кода?
                                                                                                                                                              (
                                                                                                                                                              …
                                                                                                                                                              ); exit;
                                                                                                                                                              

                                                                                                                                                              Судя по документации команды exit — она и так возьмет код последней команды, если его специально не указывать
                                                                                                                                                              Является ли важным явно указать его, или это вопрос только читаемости?
                                                                                                                                                                0
                                                                                                                                                                Я сейчас так пишу сугубо для упрощения понимания последующими поколениями. Как ни странно, в половине случаев те, кто в дальнейшем редактировали скрипты, благополучно забывали о том, что блок в скобках может закончится с ошибкой и пытались сделать "&& exit" и даже вообще убрать exit. Когда стал явно писать выход с кодом ошибки "$?", такие случаи сократились.
                                                                                                                                                                  0
                                                                                                                                                                  Спасибо.
                                                                                                                                                                  Очень предусмотрительный подход — возьму на вооружение
                                                                                                                                                      0

                                                                                                                                                      А кто нибудь поверил что будет если файл на nfs?
                                                                                                                                                      Тоже воспроизведется или нет?

                                                                                                                                                        0
                                                                                                                                                        Оно вообще всего воспроизводится, т.к. файл читается частями, и если его изменять ( а не удалять и пересоздавать), то часть данных будет прочитана новая. Если удалять и создавать заново, то резальтат зависит он ОС и файловой системы — в большинстве случаев bash будет читать старую «правильную» версию файла, но при некоторых сочетаниях может файл может как оказаться заблокированным для удаления, так и может «протухнуть» хендлер файла баша и он в случайном месте рухнет с ошибкой.
                                                                                                                                                        +3
                                                                                                                                                        Спасибо вам за перевод моей статьи! Качество текста лучше, чем у оригинала. — Том
                                                                                                                                                          0
                                                                                                                                                          То есть нужно успеть за 30 секунд поменять скрипт?
                                                                                                                                                            +1
                                                                                                                                                            Некоторые скрипты в cron могут часами работать…
                                                                                                                                                              0
                                                                                                                                                              Есть скрипты, которые вообще всегда в ходе своей работы сами себя перезаписывают (например, обновление или установка чего-то).