명령 대체를 사용할 때 개행 문자가 손실되는 이유는 무엇입니까?

명령 대체를 사용할 때 개행 문자가 손실되는 이유는 무엇입니까?

아래와 같이 link.txt라는 텍스트 파일이 있습니다.

link1
link2
link3

이 파일을 한 줄씩 반복하고 각 줄에서 작업을 수행하고 싶습니다. while 루프를 사용하여 이 작업을 수행할 수 있다는 것을 알고 있지만 배우고 있으므로 for 루프를 사용하고 싶습니다. 나는 실제로 다음과 같은 명령 대체를 사용했습니다.

a=$(cat links.txt)

그런 다음 이와 같은 루프를 사용하십시오.

for i in $a; do ###something###;done

나도 이런 걸 할 수 있어

for i in $(cat links.txt); do ###something###; done

이제 내 문제는 cat 명령 출력을 변수 a로 바꾸면 link1 link2와 link3 사이의 새 줄 문자가 제거되고 공백으로 대체된다는 것입니다.

echo $a

산출

링크 1 링크 2 링크 3

그런 다음 for 루프를 사용했습니다. 명령 대체를 수행할 때 새 줄은 항상 공백으로 대체됩니까?

인사

답변1

개행 문자는 특수 문자이기 때문에 어느 시점에서 대체됩니다. 이를 보존하려면 항상 따옴표를 사용하여 해석되도록 해야 합니다.

$ a="$(cat links.txt)"
$ echo "$a"
link1
link2
link3

이제 데이터를 조작할 때마다 따옴표를 사용하므로 개행 문자( \n)는 항상 쉘에 의해 해석되어 그대로 유지됩니다. 특정 시점에 사용하는 것을 잊어버리면 이러한 특수 문자가 손실됩니다.

공백이 포함된 줄에 루프를 사용하는 경우에도 똑같은 동작이 발생합니다. 예를 들어, 다음 파일이 주어지면...

mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt

출력은 따옴표 사용 여부에 따라 달라집니다.

$ for i in $(cat links.txt); do echo $i; done
mypath1/file
with
spaces.txt
mypath2/filewithoutspaces.txt

$ for i in "$(cat links.txt)"; do echo "$i"; done
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt

이제 따옴표를 사용하지 않으려면 특수 쉘 변수를 사용하여 쉘 필드 구분 기호( IFS)를 변경할 수 있습니다. 이 구분 기호를 개행 문자로 설정하면 대부분의 문제를 해결할 수 있습니다.

$ IFS=$'\n'; for i in $(cat links.txt); do echo $i; done
mypath1/file with spaces.txt
mypath2/filewithoutspaces.txt

완전성을 기하기 위해 명령 출력 대체에 의존하지 않는 또 다른 예가 있습니다. 얼마 후, 대부분의 사용자는 유틸리티의 동작으로 인해 이 방법이 더 안정적이라고 생각하는 것으로 나타났습니다 read.

$ cat links.txt | while read i; do echo $i; done

read다음은 매뉴얼 페이지에서 발췌한 내용입니다.

읽기 유틸리티는 표준 입력에서 한 줄을 읽어야 합니다.

read입력은 한 줄씩 이루어지기 때문에 공백이 나타날 때마다 입력이 중단되지 않을 것입니다. 출력을 파이프로 연결 하면 cat행이 잘 반복됩니다.

편집하다:사람들이 cat.제이슨 라이언그의 댓글에는 더 많은적절한셸에서 파일을 읽는 방법은 <앞서 설명한 것처럼 스트림 리디렉션( )을 사용하는 것입니다.val0x00ff의 답변은 여기에 있습니다. 그러나 질문은 "가 아니기 때문에쉘 프로그래밍에서 파일을 읽고 처리하는 방법", 내 대답은 나머지 것보다 인용 행위에 더 중점을 둡니다.

답변2

쉘이 이미 실행되었기 때문에 개행 문자가 손실됩니다.필드 분할명령 대체 후.

POSIX에서는명령 대체부분:

쉘은 하위 쉘 환경(쉘 실행 환경 참조)에서 명령을 실행하고 명령 대체(명령 텍스트 + "$()" 또는 백틱)를 명령의 표준 출력으로 대체하여 명령 대체를 확장해야 합니다. 여러 문자의 시퀀스입니다.포함된 문자는 출력이 끝나기 전에 제거되어서는 안 됩니다. 그러나 IFS 값과 유효한 참조에 따라 필드 구분 기호로 처리되어 필드 분할 중에 제거될 수 있습니다.. 출력에 null 바이트가 포함되어 있으면 동작이 지정되지 않습니다.

기본값 IFS(적어도 bash):

$ printf '%q\n' "$IFS"
$' \t\n'

귀하의 경우 큰따옴표를 설정하거나 사용하지 않으므로 IFS필드 분할 중에 개행 문자가 제거됩니다.

예를 들어 공백으로 설정하여 줄바꿈을 유지할 수 있습니다 IFS.

$ IFS=
$ a=$(cat links.txt)
$ echo "$a"
link1
link2
link3

답변3

내 요점을 강조하기 위해 for루프가 반복됩니다.성격. 파일이 다음과 같은 경우:

one two
three four

그러면 다음이 발행됩니다.4개선:

for word in $(cat file); do echo "$word"; done

반복철사파일에서 다음을 수행합니다.

while IFS= read -r line; do
    # do something with "$line" <-- quoted almost always
done < file

답변4

개행 문자는 작동 방식 때문에 공백으로 대체됩니다 echo. 인수를 공백에 연결합니다. echo매개변수 구분 기호를 공백으로 바꿉니다. 실제로 for원하는 것은 무엇이든 반복할 수 있지만 먼저 필드 구분 기호를 지정해야 합니다.

string=abababababababababababa IFS=a        
for c in $string
do printf %s "$c"
done

산출

bbbbbbbbbbb

그러나 이는 루프에만 고유한 동작이 아닙니다 for. 모든 필드 분할 확장에서 발생합니다.

printf %s $string
bbbbbbbbbbb

예를 들어, 파일에서 비어 있지 않은 줄의 처음 10바이트만 인쇄하려는 경우...

###original:
first "line"
<second>"line"
<second>"line"
<second>line and so on%
(IFS='                                                       
'; printf %.10s\\n $(cat file))
###output
first "lin
<second>"l
<second>"l
<second>li

제가 명시한 이유는공백이 아닌위 - \newline은 입니다 $IFS. 두 개 이상이 연속으로 발생하면 다른 모든 항목은 빈 인수를 제공하지만 일련의 공백, 탭 또는 줄 바꿈은 단일 필드로만 계산됩니다.

예를 들어:

(IFS=0;printf 'ten lines!%s\n' $(printf "%010d"))

ten lines!
ten lines!
ten lines!
ten lines!
ten lines!
ten lines!
ten lines!
ten lines!
ten lines!
ten lines!

하지만...

(IFS=\ ;printf 'one line%s\n' $(printf "%010s"))
one line

두 경우 모두 printf10개의 패딩 문자가 인쇄됩니다. 첫 번째 경우에는 10개의 0이 인쇄되고 두 번째 경우에는 10개의 공백이 인쇄됩니다. 첫 번째 경우에는 각 0이 빈 필드를 생성하고 두 번째 경우에는 printf10개의 빈 인수를 얻습니다. 각 인수에 대해 해당 형식 문자열이 기록되지만 두 번째 경우에는 모든 공백이 전혀 의미 없이 인쇄됩니다.

그렇지 않다는 점에 유의해야 합니다.오직쉘은 따옴표가 없는 확장을 사용하여 필드 유형을 생성합니다. 기본적으로전반적인 상황. 다음과 같이 하십시오:

for line in $(cat file)

이는 일부 행에 실제 파일과 일치하는 쉘 전역 변수가 포함될 가능성이 높기 때문에 매우 예상치 못한 결과를 초래할 수 있으며 갑자기 $line입력 행이 더 이상 참조되지 않고 오히려 디스크의 파일 이름이 참조됩니다.

$IFS분할에 사용할 계획이라면 다음과 같습니다.언제나좋은 생각은 다음과 같습니다:

set -f

...먼저, glob을 수행할 때 쉘에 glob을 수행하지 않도록 지시합니다. 완료되면 를 사용하여 다시 활성화할 수 있습니다 set +f.

관련 정보