Ответ 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. Это можно сделать несколькими способами:
-
Это можно сделать с
dos2unix
программыdos2unix
:dos2unix filename
-
Откройте файл в текстовом редакторе с поддержкой (Sublime, Notepad++, а не в Notepad) и настройте его для сохранения файлов с окончаниями строк Unix, например, с Vim, перед (повторным) сохранением выполните следующую команду:
:set fileformat=unix
-
Если у вас есть версия утилиты
sed
которая поддерживает-i
или--in-place
, например, GNUsed
, вы можете запустить следующую команду, чтобы убрать концевые возвраты каретки:sed -i 's/\r$//' filename
В других версиях
sed
вы можете использовать перенаправление вывода для записи в новый файл. Обязательно используйте другое имя файла для цели перенаправления (его можно переименовать позже).sed 's/\r$//' filename > filename.unix
-
Точно так же фильтр перевода
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 будет пытаться интерпретировать такие файлы.