Являются ли сценарии оболочки чувствительными к кодированию и окончаниям строк?

Я делаю приложение NW.js на Mac и хочу запустить приложение в режиме dev, дважды щелкнув значок. Первый шаг, я пытаюсь сделать мою оболочку script работать.

Используя VSCode в Windows (я хотел получить время), я создал файл run-nw в корне моего проекта, содержащий следующее:

#!/bin/bash

cd "src"
npm install

cd ..
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &

но я получаю этот вывод:

$ sh ./run-nw

: command not found  
: No such file or directory  
: command not found  
: No such file or directory  

Usage: npm <command>

where <command> is one of:  (snip commands list)

(snip npm help)

[email protected] /usr/local/lib/node_modules/npm  
: command not found  
: No such file or directory  
: command not found

Я действительно не понимаю:

  • кажется, что он принимает пустые строки в качестве команд. В моем редакторе (VSCode) я попытался заменить \r\n на \n (в случае, если \r создает проблемы), но ничего не меняет.
  • кажется, что он не находит папки (с инструкцией dirname или без нее), или, может быть, она не знает о команде cd?
  • кажется, что он не понимает аргумент install для npm
  • часть, которая меня действительно извращает, заключается в том, что она все еще запускает приложение (если я сделал npm install вручную)...

Невозможно заставить его работать правильно, и, подозревая что-то странное с самим файлом, я создал новый непосредственно на Mac, используя vim на этот раз. Я ввел те же самые инструкции и... теперь он работает без каких-либо проблем.
Разница в двух файлах показывает ровно нулевую разницу.

В чем разница? Что может сделать первый script не работать? Как я могу узнать?

Update

Следуя принятым рекомендациям по ответам, после того, как неправильные окончания строк вернулись, я проверил несколько вещей. Оказывается, так как я скопировал свой ~/.gitconfig с моей машины Windows, у меня был autocrlf=true, поэтому каждый раз, когда я модифицировал файл bash под Windows, он снова устанавливал окончания строки на \r\n.
Таким образом, помимо запуска dos2unix (который вам нужно будет установить с помощью Homebrew на mac), если вы используете Git, проверьте свою конфигурацию.

Ответ 1

Да. Скрипты Bash чувствительны к окончанию строк, как в самом скрипте, так и в данных, которые он обрабатывает. Они должны иметь конец строки в стиле Unix, то есть каждая строка заканчивается символом перевода строки (десятичное число 10, шестнадцатеричное 0A в ASCII).

DOS/Windows окончания строк в скрипте

В конце строки в стиле Windows или DOS каждая строка заканчивается символом возврата каретки, за которым следует символ перевода строки. Если файл сценария был сохранен с окончанием строки Windows, Bash видит файл как

#!/bin/bash^M
^M
cd "src"^M
npm install^M
^M
cd ..^M
./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &^M

Примечание. Я использовал обозначение каретки для представления непечатаемых символов, т. ^M используется для представления символов возврата каретки (представленных как \r в других контекстах); это та же самая техника, которую использовали cat -v и Vim.

В этом случае возврат каретки (^M или \r) не рассматривается как пробел. Bash интерпретирует первую строку после шебанга (состоящего из одного символа возврата каретки) как имя команды/программы для запуска.

  • Поскольку нет команды с именем ^M, она печатает : command not found
  • Поскольку нет каталога с именем "src"^M (или src^M), он печатает : No such file or directory
  • Он передает install^M вместо install в качестве аргумента npm что заставляет npm жаловаться.

DOS/Windows окончания строк во входных данных

Как и выше, если у вас есть входной файл с возвратом каретки:

hello^M
world^M

тогда это будет выглядеть совершенно нормально в редакторах и при записи на экран, но инструменты могут давать странные результаты. Например, grep не сможет найти строки, которые явно присутствуют:

$ grep 'hello$' file.txt || grep -x "hello" file.txt
(no match because the line actually ends in ^M)

Добавленный текст вместо этого перезапишет строку, потому что возврат каретки перемещает курсор в начало строки:

$ sed -e 's/$/!/' file.txt
!ello
!orld

Сравнение строк может показаться неудачным, даже если при записи на экран строки выглядят одинаково:

$ a="hello"; read b < file.txt
$ if [[ "$a" = "$b" ]]
  then echo "Variables are equal."
  else echo "Sorry, $a is not equal to $b"
  fi

Sorry, hello is not equal to hello

Решения

Решение состоит в том, чтобы преобразовать файл в конец строки в стиле Unix. Это можно сделать несколькими способами:

  1. Это можно сделать с dos2unix программы dos2unix:

    dos2unix filename
    
  2. Откройте файл в текстовом редакторе с поддержкой (Sublime, Notepad++, а не в Notepad) и настройте его для сохранения файлов с окончаниями строк Unix, например, с Vim, перед (повторным) сохранением выполните следующую команду:

    :set fileformat=unix
    
  3. Если у вас есть версия утилиты sed которая поддерживает -i или --in-place, например, GNU sed, вы можете запустить следующую команду, чтобы убрать концевые возвраты каретки:

    sed -i 's/\r$//' filename
    

    В других версиях sed вы можете использовать перенаправление вывода для записи в новый файл. Обязательно используйте другое имя файла для цели перенаправления (его можно переименовать позже).

    sed 's/\r$//' filename > filename.unix
    
  4. Точно так же фильтр перевода tr можно использовать для удаления нежелательных символов из его входных данных:

    tr -d '\r' <filename >filename.unix
    

Cygwin Bash

С портом Bash для Cygwin существует настраиваемая опция igncr которую можно настроить так, чтобы игнорировать возврат каретки в igncr строки (предположительно, потому что многие из ее пользователей используют собственные программы Windows для редактирования своих текстовых файлов). Это можно включить для текущей оболочки, запустив set -o igncr.

Установка этого параметра применима только к текущему процессу оболочки, поэтому может быть полезна при поиске файлов с посторонними возвратами каретки. Если вы регулярно сталкиваетесь со сценариями оболочки с окончанием строки DOS и хотите, чтобы этот параметр был установлен постоянно, вы можете установить переменную окружения SHELLOPTS (все заглавные буквы) для включения igncr. Эта переменная окружения используется Bash для установки параметров оболочки при запуске (перед чтением любых файлов запуска).

Полезные утилиты

Утилита file полезна для быстрого просмотра того, какие окончания строк используются в текстовом файле. Вот что он печатает для каждого типа файла:

  • Концы строк Unix: Bourne-Again shell script, ASCII text executable
  • Концы строк Mac: Bourne-Again shell script, ASCII text executable, with CR line terminators
  • Окончание строк DOS: Bourne-Again shell script, ASCII text executable, with CRLF line terminators

GNU-версия утилиты cat имеет -v, --show-nonprinting которая отображает -v, --show-nonprinting символы.

Утилита dos2unix специально предназначена для преобразования текстовых файлов между окончаниями строк Unix, Mac и DOS.

Полезные ссылки

В Википедии есть отличная статья, охватывающая множество различных способов пометить конец строки текста, историю таких кодировок и то, как обрабатываются переводы строк в разных операционных системах, языках программирования и интернет-протоколах (например, FTP).

Файлы с классическим окончанием строки Mac OS

В Classic Mac OS (до -o SX) каждая строка заканчивалась символом возврата каретки (десятичное 13, шестнадцатеричный 0D в ASCII). Если файл сценария был сохранен с такими окончаниями строк, Bash увидит только одну длинную строку, например:

#!/bin/bash^M^Mcd "src"^Mnpm install^M^Mcd ..^M./tools/nwjs-sdk-v0.17.3-osx-x64/nwjs.app/Contents/MacOS/nwjs "src" &^M

Поскольку эта единственная длинная строка начинается с восьмиугольника (#), Bash рассматривает строку (и весь файл) как один комментарий.

Примечание. В 2001 году Apple выпустила Mac OS X, основанную на операционной системе NeXTSTEP, основанной на BSD. В результате OS X также использует только конец строки LF -o Unix-стиля, и с тех пор текстовые файлы, оканчивающиеся CR, стали чрезвычайно редкими. Тем не менее, я думаю, что стоит показать, как Bash будет пытаться интерпретировать такие файлы.

Ответ 2

Еще один способ избавиться от нежелательного символа CR ('\ r') - запустить команду tr, например:

$ tr -d '\r' < dosScript.py > nixScript.py

Ответ 3

В продуктах JetBrains (PyCharm, PHPStorm, IDEA и т.д.) Вам необходимо click на CRLF/LF переключаться между двумя типами разделителей строк (\r\n и \n).

enter image description here enter image description here

Ответ 4

Исходя из дубликата, если проблема в том, что у вас есть файлы, имена которых содержат ^M в конце, вы можете переименовать их с помощью

for f in *$'\r'; do
    mv "$f" "${f%$'\r'}"
done

Вы должным образом хотите исправить то, что привело к тому, что у этих файлов были вначале неправильные имена (вероятно, сценарий, который их создал, должен быть dos2unix а затем перезапущен?), Но иногда это неосуществимо.

Ответ 5

Самый простой способ для MAC/Linux - создать файл с помощью команды "touch", открыть этот файл с помощью редактора VI или VIM, вставить свой код и сохранить. Это автоматически удалит символы Windows.