MSYS2에서처럼 Linux에서 CRLF(캐리지 리턴)를 사용하여 Bash 스크립트를 처리합니까?

MSYS2에서처럼 Linux에서 CRLF(캐리지 리턴)를 사용하여 Bash 스크립트를 처리합니까?

다음과 같은 간단한 스크립트가 있다고 가정해 보겠습니다 tmp.sh.

echo "testing"
stat .
echo "testing again"

사소하지만 \r\n줄 끝으로 (즉, 캐리지 리턴 + 줄 바꿈인 CRLF)가 있습니다. 웹페이지는 줄 끝을 유지하지 않으므로 다음은 16진수 덤프입니다.

$ hexdump -C tmp.sh 
00000000  65 63 68 6f 20 22 74 65  73 74 69 6e 67 22 0d 0a  |echo "testing"..|
00000010  73 74 61 74 20 2e 0d 0a  65 63 68 6f 20 22 74 65  |stat ...echo "te|
00000020  73 74 69 6e 67 20 61 67  61 69 6e 22 0d 0a        |sting again"..|
0000002e

이제 스크립트는 Windows의 MSYS2에서 시작되고 개발되었기 때문에 CRLF 줄 끝이 있습니다. 따라서 MSYS2의 Windows 10에서 실행하면 예상되는 결과를 얻습니다.

$ bash tmp.sh
testing
  File: .
  Size: 0               Blocks: 40         IO Block: 65536  directory
Device: 8e8b98b6h/2391513270d   Inode: 281474976761067  Links: 1
Access: (0755/drwxr-xr-x)  Uid: (197609/      USER)   Gid: (197121/    None)
Access: 2020-04-03 10:42:53.210292000 +0200
Modify: 2020-04-03 10:42:53.210292000 +0200
Change: 2020-04-03 10:42:53.210292000 +0200
 Birth: 2019-02-07 13:22:11.496069300 +0100
testing again

그러나 이 스크립트를 Ubuntu 18.04 시스템에 복사하고 거기서 실행하면 다른 결과가 나타납니다.

$ bash tmp.sh
testing
stat: cannot stat '.'$'\r': No such file or directory
testing again

같은 줄 끝이 있는 다른 스크립트의 Ubuntu bash에서도 다음 오류가 발생했습니다.

line 6: $'\r': command not found

... 아마도 빈 줄에서 나온 것 같습니다.

따라서 우분투의 무언가가 캐리지 리턴에 질식하는 것이 분명합니다. 나는 보았다BASH 및 캐리지 리턴 동작:

Bash와는 아무 관련이 없습니다. \r 및 \n은 Bash가 아닌 터미널에서 해석됩니다.

... 하지만 이는 명령줄에 그대로 입력된 항목에만 적용되는 것 같습니다. 여기서 \r및 는 \n이미 스크립트 자체에 입력되어 있으므로 Bash가 \r여기를 해석해야 합니다.

Ubuntu의 Bash 버전은 다음과 같습니다.

$ bash --version
GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu)

... 그리고 MSYS2의 Bash 버전은 다음과 같습니다.

$ bash --version
GNU bash, version 4.4.23(2)-release (x86_64-pc-msys)

(그다지 별 차이 없어 보이는데...)

\r어쨌든 내 질문은 - 우분투/리눅스에서 Bash를 (말하자면) "인쇄 가능한 문자"로 해석하는 대신 무시하도록 설득하는 방법이 있습니까? bash가 그렇게 해석하는 유효한 명령의 일부)? 편집하다:없이스크립트 자체를 변환해야 함(따라서 git에서 그런 방식으로 검사하면 CRLF 줄 끝으로 동일하게 유지됨)

EDIT2: 저는 이 방법을 선호합니다. 왜냐하면 저와 함께 작업하는 다른 사람들이 Windows 텍스트 편집기에서 스크립트를 다시 열고 잠재적으로 \r\n스크립트에 다시 도입하여 커밋할 수 있기 때문입니다. 그런 다음 우리는 저장소를 오염시키는 \r\n변환 에 지나지 않는 끝없는 커밋 스트림으로 끝날 수 있습니다 .\n

EDIT2: @Kusalananda가 언급된 의견 dos2unix( sudo apt install dos2unix); 다음과 같이 작성하세요.

$ dos2unix tmp.sh 
dos2unix: converting file tmp.sh to Unix format...

... 파일을 그 자리에서 변환합니다. stdout으로 출력하려면 stdin 리디렉션을 설정해야 합니다.

$ dos2unix <tmp.sh | hexdump -C
00000000  65 63 68 6f 20 22 74 65  73 74 69 6e 67 22 0a 73  |echo "testing".s|
00000010  74 61 74 20 2e 0a 65 63  68 6f 20 22 74 65 73 74  |tat ..echo "test|
00000020  69 6e 67 20 61 67 61 69  6e 22 0a                 |ing again".|
0000002b

... 그리고 원칙적으로 우분투에서 이것을 실행할 수 있는데, 이 경우에는 작동하는 것 같습니다:

$ dos2unix <tmp.sh | bash
testing
  File: .
  Size: 20480       Blocks: 40         IO Block: 4096   directory
Device: 816h/2070d  Inode: 1572865     Links: 27
Access: (1777/drwxrwxrwt)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2020-04-03 11:11:00.309160050 +0200
Modify: 2020-04-03 11:10:58.349139481 +0200
Change: 2020-04-03 11:10:58.349139481 +0200
 Birth: -
testing again

그러나 기억해야 할 약간 지저분한 명령을 제외하면 stdin은 더 이상 터미널이 아니기 때문에 bash 의미도 변경됩니다. 이것은 이 사소한 예에서 작동했을 수도 있지만 예를 참조하십시오.https://stackoverflow.com/questions/23257247/pipe-a-script-into-bash예를 들어 더 큰 문제가 있습니다.

답변1

내가 아는 한 Bash에게 Windows 스타일 줄 끝을 허용하도록 지시할 수 있는 방법은 없습니다.

Windows와 관련된 상황에서 일반적인 관행은 구성 플래그를 사용하여 커밋할 때 줄 끝을 자동으로 변환하는 Git의 기능에 의존하는 것입니다 autocrlf. 예를 들어 참조줄 끝 부분에 대한 GitHub 문서, 이는 GitHub에만 국한되지 않습니다. 이렇게 하면 파일이 저장소에서 Unix 스타일 줄 끝으로 커밋되고 각 클라이언트 플랫폼에 맞게 변환됩니다.

(반대의 문제는 문제가 되지 않습니다. MSYS2는 Windows의 Unix 스타일 줄 끝에서 잘 작동합니다.)

답변2

당신은 사용해야합니다binfmt_misc그러기 위해 [1].

먼저 로 시작하는 파일을 처리하는 매직을 정의한 #! /bin/bash<CR><LF>다음 이에 대한 실행 가능한 인터프리터를 만듭니다. 인터프리터는 다른 스크립트일 수 있습니다.

INTERP=/path/to/bash-crlf

echo ",bash-crlf,M,,#! /bin/bash\x0d\x0a,,$INTERP," > /proc/sys/fs/binfmt_misc/register
cat > "$INTERP" <<'EOT'; chmod 755 "$INTERP"
#! /bin/bash
script=$1; shift; exec bash <(sed 's/\r$//' "$script") "$@"
EOT

테스트해보세요:

$ printf '%s\r\n' '#! /bin/bash' pwd >/tmp/foo; chmod 755 /tmp/foo
$ cat -v /tmp/foo
#! /bin/bash^M
pwd^M
$ /tmp/foo
/tmp

샘플 인터프리터에는 두 가지 문제가 있습니다.1.찾을 수 없는 파일(파이프)을 통해 스크립트를 전달하기 때문에 bash는 스크립트를 바이트 단위로 매우 비효율적으로 읽습니다.2.모든 오류 메시지는 /dev/fd/63원본 스크립트의 이름 대신에 언급되거나 유사합니다.

[1] 물론, binfmt_misc를 사용하는 대신 /bin/bash^M인터프리터에 대한 기호 링크를 만들 수 있으며 이는 OpenBSD와 같은 다른 시스템에서도 작동합니다.

ln -s /path/to/bash-crlf $'/bin/bash\r'

그러나 Linux에서 shebanged 실행 파일은 binfmt_misc에 비해 이점이 없으며 시스템 디렉터리에 쓰레기를 넣는 것은 올바른 전략이 아니며 시스템 관리자가 고개를 흔들게 될 것입니다 ;-)

답변3

좋아, 다음을 통해 해결 방법을 찾았습니다.

"접합된" 심볼릭 링크

최신 유닉스 시스템에는 저장 방식에 관계없이 임의의 데이터를 파일로 표시하는 방법이 있습니다.퓨즈. FUSE를 사용하면 파일에 대한 모든 작업(생성, 열기, 읽기, 쓰기, 디렉터리 나열 등)이 프로그램의 일부 코드를 호출하고 해당 코드는 원하는 작업을 수행할 수 있습니다. 보다실제로 명령인 가상 파일을 생성합니다.. 당신은 시험해 볼 수 있습니다스크립트또는퓨즈펠트, 또는 야심찬 느낌이 든다면 직접 굴려보세요.

... 그리고실제로 명령인 가상 파일을 생성합니다.

당신은명명된 파이프.

따라서 접근 방식은 다음과 같습니다. 명명된 파이프를 만들고 dos2unix출력한 다음 bash명명된 파이프를 호출합니다.

여기에는 CRLF 줄이 ; tmp.sh로 끝나는 원본이 있습니다 . /tmp먼저 명명된 파이프를 만들어 보겠습니다.

tmp$ mkfifo ftmp.sh

이제 이 명령을 실행하면 다음과 같습니다.

tmp$ dos2unix <tmp.sh >ftmp.sh

... 당신은 그것이 차단된다는 것을 알게 될 것입니다; 그렇다면 다음과 같이 말하세요:

~$ cat /tmp/ftmp.sh | hexdump -C
00000000  65 63 68 6f 20 22 74 65  73 74 69 6e 67 22 0a 73  |echo "testing".s|
00000010  74 61 74 20 2e 0a 65 63  68 6f 20 22 74 65 73 74  |tat ..echo "test|
00000020  69 6e 67 20 61 67 61 69  6e 22 0a                 |ing again".|
0000002b

... 변환이 완료되었음을 알 수 있습니다. cat명령이 실행된 후 dos2unix <tmp.sh >ftmp.sh이전에 차단했던 명령이 종료되었습니다.

dos2unix따라서 "무한" while 루프에서 명명된 파이프에 대한 쓰기를 설정할 수 있습니다 .

tmp$ while [ 1 ] ; do dos2unix <tmp.sh >ftmp.sh ; done

... 그리고 "단단한" 루프이더라도 문제가 되지 않습니다. 대부분의 경우 while 루프 내부의 명령이 차단되기 때문입니다.

그러면 다음과 같이 할 수 있습니다.

~$ bash /tmp/ftmp.sh
testing
  File: .
  Size: 4096        Blocks: 8          IO Block: 4096   directory
Device: 801h/2049d  Inode: 5276132     Links: 7
...
testing again
$

...그리고 분명히 스크립트는 정상적으로 실행됩니다.

이 접근 방식의 좋은 점은 원본을 tmp.sh텍스트 편집기에서 열 수 있다는 것입니다. CRLF 종료로 새 코드를 작성한 다음 저장하십시오 tmp.sh. Linux에서 실행하면 bash /tmp/ftmp.sh최신 저장된 버전이 실행됩니다.

문제는 read -p "Enter user: " user실제 터미널 stdin에 의존하는 명령이 실패한다는 것입니다. 아니면 실패하지 말고, 시도한다면 이렇게 말하세요./tmp/tmp.sh

echo "testing"
stat .
echo "testing again"
read -p "Enter user: " user
echo "user is: $user"

... 그러면 다음과 같이 출력됩니다.

$ bash /tmp/ftmp.sh
testing
  File: .
  Size: 4096        Blocks: 8          IO Block: 4096   directory
...
 Birth: -
testing again
Enter user: tyutyu
user is: tyutyu
testing
  File: .
  Size: 4096        Blocks: 8          IO Block: 4096   directory
...
 Birth: -
testing again
Enter user: asd
user is: asd
testing
...

read -p ...... 등등 - 즉, 터미널 키보드의 stdin은 올바르게 해석되지만 어떤 이유로 스크립트가 루핑을 시작하고 처음부터 계속해서 실행됩니다( 명령이 없으면 발생하지 않음). 원래 tmp.sh). 아마도 리디렉션 문제가 있을 수 있습니다(예: 루프 명령 에 일부 0>1&또는 무엇이든while.sh 추가; 실제로 루프가 시작된 스크립트가 있었고 스크립트 끝에 wget명시적인 항목을 추가하면 스크립트 루프를 중지하는 것처럼 보였습니다). 이것도 처리할 수 있지만 지금까지 제가 사용해야 하는 스크립트에는 유사한 명령이 없으므로 이 접근 방식이 저에게 적합할 수 있습니다.exit.shread -p

답변4

Bash 스크립트의 모든 줄 끝에 해시(#)를 삽입할 수 있습니다. 이런 방식으로 Unix의 쉘은 CR을 주석으로 간주하고 신경 쓰지 않습니다.

"16진수 말하기", 모든 줄은 다음으로 끝나야 합니다.

0x23 0x0D 0x0A

예:

echo "testing" #
stat . #
echo "testing again" #

관련 정보