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

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

Используя 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)

npm@3.10.3 /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. Я ввел точно такие же инструкции, и... теперь это работает без каких-либо проблем.
Разница между двумя файлами показывает абсолютно нулевую разницу.

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

Обновить

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

16 ответов

Решение

Да. Скрипты 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 и Вим.

В этом случае возврат каретки (^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 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 опция, которая может быть установлена, чтобы игнорировать возврат каретки в конце строки (предположительно, потому что многие из ее пользователей используют собственные программы Windows для редактирования своих текстовых файлов). Настройка этого параметра применяется к текущему процессу оболочки, поэтому может быть полезна при поиске файлов с посторонними возвратами каретки.

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

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 опция, которая отображает непечатаемые символы.

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

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

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

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

В Classic Mac OS (до OS X) каждая строка заканчивалась символом возврата каретки (десятичное 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 в стиле Unix, и с тех пор текстовые файлы, оканчивающиеся CR, стали чрезвычайно редкими. Тем не менее, я думаю, что стоит показать, как Bash будет пытаться интерпретировать такие файлы.

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

Я пытался запустить свой докер-контейнер из Windows и получил следующее:

Bash script and /bin/bash^M: bad interpreter: No such file or directory

Я использовал git bash, и проблема была в конфигурации git, тогда я просто выполнил шаги, указанные ниже, и все сработало. Он настроит Git, чтобы не преобразовывать окончания строк при оформлении заказа:

  1. git config --global core.autocrlf input
  2. удалить свой локальный репозиторий
  3. клонировать его снова.

Большое спасибо Джейсону Хармону за эту ссылку:https://forums.docker.com/t/error-while-running-docker-code-in-powershell/34059/6 while-running-docker-code-in-powershell/ 34059/6

До этого я пробовал вот это, не сработало:

  1. dos2unix scriptname.sh
  2. sed -i -e 's/\r$//' scriptname.sh
  3. sed -i -e 's/^M$//' scriptname.sh

Если вы используете команду для чтения из файла (или канала), который находится (или может быть) в формате DOS / Windows, вы можете воспользоваться тем фактом, что будет обрезать пробелы в начале и в конце строк. Если вы скажете ему, что возврат каретки - это пробел (добавив их к переменной), он обрежет их с концов строк.

В bash (или zsh или ksh) это означает, что вы должны заменить эту стандартную идиому:

      IFS= read -r somevar    # This will not trim CR

с этим:

      IFS=$'\r' read -r somevar    # This *will* trim CR

(Обратите внимание -r опция не связана с этим, просто обычно рекомендуется избегать искажения обратной косой черты.)

Если вы не используете IFS= префикс (например, потому что вы хотите разбить данные на поля), тогда вы должны заменить это:

      read -r field1 field2 ...    # This will not trim CR

с этим:

      IFS=$' \t\n\r' read -r field1 field2 ...    # This *will* trim CR

Если вы используете оболочку, которая не поддерживает $'...'режим цитирования (например, тире, / bin / sh по умолчанию в некоторых дистрибутивах Linux), или ваш скрипт даже может запускаться с такой оболочкой, тогда вам нужно немного усложнить:

      cr="$(printf '\r')"
IFS="$cr" read -r somevar    # Read trimming *only* CR
IFS="$IFS$cr" read -r field1 field2 ...    # Read trimming CR and whitespace, and splitting fields

Обратите внимание, что обычно, когда вы меняете IFS, вы должны вернуть его в нормальное состояние как можно скорее, чтобы избежать странных побочных эффектов; но во всех этих случаях это префикс к read command, поэтому он влияет только на эту одну команду и не требует сброса впоследствии.

Поскольку используется VS Code, мы можем видеть CRLF или LF в правом нижнем углу в зависимости от того, что используется, и если мы щелкнем по нему, мы можем переключаться между ними (LF используется в примере ниже):

Мы также можем использовать команду «Изменить последовательность конца строки» из палитры команд. Все, что легче запомнить, поскольку они функционально одинаковы.

Много ссылок на git, но не на перенормировку концов строк. Просто перейдите в корень вашего репо и запустите:

      git add --renormalize .

Повторно будут проверены только те файлы, для которых необходимо обновить окончания строк. Будет видно, что в файлах нет изменений, поскольку окончания строк невидимы.

Я столкнулся с этой проблемой, когда использую git с WSL. git имеет функцию, при которой он изменяет конец строки файлов в соответствии с используемой вами ОС, в Windows он проверяет, что окончание строки/r/n который несовместим с Linux, который использует только /n.

Вы можете решить эту проблему, добавив имя файла .gitattributes в корневой каталог git и добавьте следующие строки:

config/* text eol=lf
run.sh text eol=lf

В этом примере все файлы внутри config каталог будет иметь только конец строки и run.sh файл.

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

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

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

За Notepad++пользователи, это может быть решено с помощью:

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

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

Для пользователей IntelliJ вот решение для написания сценария Linux.
Используйте LF — Unix и masOS (\n)

Скрипты могут вызывать друг друга. Еще лучшее волшебное решение — преобразовать все скрипты в папку/подпапки:

      find . -name "*.sh" -exec sed -i -e 's/\r$//' {} +

Вы можете использовать dos2unixтоже, но на многих серверах он не установлен по умолчанию.

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

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

Из-за этой проблемы я много раз повреждал bash-скрипты.

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

Откройте vim с помощью сценария оболочки и запустите эту команду

      :set ff=unix

Затем отредактируйте свои .gitattributes, чтобы получить постоянное исправление.

Для полноты картины я укажу еще одно решение, которое может решить эту проблему навсегда без необходимости постоянно запускать dos2unix:

sudo ln -s /bin/bash `printf 'bash\r'`
Другие вопросы по тегам