방금 변수를 할당했지만 echo $ variable은 다른 것을 보여줍니다.


104

다음은 echo $var방금 할당 된 값과 다른 값을 표시 할 수 있는 일련의 사례 입니다. 이는 할당 된 값이 "큰 따옴표", "작은 따옴표"또는 따옴표없는 여부에 관계없이 발생합니다.

셸에서 내 변수를 올바르게 설정하려면 어떻게해야합니까?

별표

예상 출력은 /* Foobar is free software */이지만 대신 파일 이름 목록이 표시됩니다.

$ var="/* Foobar is free software */"
$ echo $var 
/bin /boot /dev /etc /home /initrd.img /lib /lib64 /media /mnt /opt /proc ...

대괄호

예상 값은 [a-z]이지만 때로는 대신 단일 문자가 표시됩니다!

$ var=[a-z]
$ echo $var
c

줄 바꿈 (줄 바꿈)

예상 값은 별도의 줄 목록이지만 대신 모든 값이 한 줄에 있습니다!

$ cat file
foo
bar
baz

$ var=$(cat file)
$ echo $var
foo bar baz

여러 공간

신중하게 정렬 된 표 머리글을 예상했지만 대신 여러 공백이 사라지거나 하나로 축소됩니다!

$ var="       title     |    count"
$ echo $var
title | count

두 개의 탭으로 구분 된 값을 예상했지만 대신 두 개의 공백으로 구분 된 값을 얻습니다!

$ var=$'key\tvalue'
$ echo $var
key value

2
해주셔서 감사합니다. 줄 바꿈이 자주 발생합니다. 그래서 var=$(cat file)괜찮지 만 echo "$var"필요합니다.
snd

3
BTW, 이것은 또한 BashPitfalls # 14입니다 : mywiki.wooledge.org/BashPitfalls#echo_.24foo
Charles Duffy


답변:


139

위의 모든 경우에서 변수가 올바르게 설정되었지만 올바르게 읽히지 않았습니다! 올바른 방법은 다음을 참조 할 때 큰 따옴표사용하는 것입니다 .

echo "$var"

이것은 주어진 모든 예에서 예상 값을 제공합니다. 항상 변수 참조를 인용하십시오!


왜?

따옴표 가없는 변수는 다음과 같습니다.

  1. 값이 공백에서 여러 단어로 분할 되는 필드 분할을 수행 합니다 (기본값).

    전에: /* Foobar is free software */

    후 : /*, Foobar, is, free, software,*/

  2. 이러한 각 단어는 패턴이 일치하는 파일로 확장되는 경로 이름 확장 을 거치게됩니다 .

    전에: /*

    후 : /bin, /boot, /dev, /etc, /home, ...

  3. 마지막으로, 모든 인수는 그들을 기록하는, 에코에 전달되는 하나의 공백으로 구분 주고,

    /bin /boot /dev /etc /home Foobar is free software Desktop/ Downloads/

    변수의 값 대신.

변수를 인용 하면 다음과 같습니다.

  1. 그 가치로 대체하십시오.
  2. 2 단계는 없습니다.

그렇기 때문에 특별히 단어 분할 및 경로 이름 확장이 필요하지 않는 한 항상 모든 변수 참조를 인용 해야합니다 . shellcheck 와 같은 도구 가 도움이 될 수 있으며 위의 모든 경우에 따옴표 누락에 대해 경고합니다.


항상 작동하지 않습니다. 예를 들어 보겠습니다. paste.ubuntu.com/p/8RjR6CS668
recolic

1
예, $(..)후행 줄 바꿈을 제거합니다. var=$(cat file; printf x); var="${var%x}"이를 해결 하는 데 사용할 수 있습니다 .
그 다른 사람

17

왜 이런 일이 발생하는지 알고 싶을 수 있습니다. 다른 사람의 훌륭한 설명 과 함께 셸 스크립트가 공백이나 기타 특수 문자로 인해 질식하는 이유에 대한 참조를 찾으십시오 . 작성한 유닉스 및 리눅스 :

왜 글을 써야 "$foo"합니까? 따옴표 없이는 어떻게 되나요?

$foo"변수의 값을 취한다"는 의미가 아닙니다 foo. 이것은 훨씬 더 복잡한 것을 의미합니다.

  • 먼저 변수의 값을 취하십시오.
  • 필드 분할 : 해당 값을 공백으로 구분 된 필드 목록으로 처리하고 결과 목록을 작성합니다. 변수가 포함되어 있으면, 예를 들면, foo * bar ​이 단계의 결과는 3 요소 목록 foo, *,bar .
  • 파일 이름 생성 : 각 필드를 glob, 즉 와일드 카드 패턴으로 취급하고이 패턴과 일치하는 파일 이름 목록으로 대체합니다. 패턴이 어떤 파일과도 일치하지 않으면 수정되지 않은 상태로 유지됩니다. 이 예에서는 foo현재 디렉토리의 파일 목록, 마지막으로 bar. 현재 디렉토리가 비어있는 경우, 결과는 foo, *, bar.

결과는 문자열 목록입니다. 쉘 구문에는 목록 컨텍스트와 문자열 컨텍스트의 두 가지 컨텍스트가 있습니다. 필드 분할 및 파일 이름 생성은 목록 컨텍스트에서만 발생하지만 대부분의 경우입니다. 큰 따옴표는 문자열 컨텍스트를 구분합니다. 큰 따옴표로 묶인 전체 문자열은 분할되지 않는 단일 문자열입니다. (예외 : "$@"위치 매개 변수 목록으로 확장하는 경우, 예를 들어 위치 매개 변수가 세 개인 경우 "$@"와 동일합니다 "$1" "$2" "$3". $ *와 $ @의 차이점무엇입니까? 참조 )

$(foo)또는 로 명령 대체도 마찬가지 입니다 `foo`. 참고로, 사용하지 마십시오 `foo`. 인용 규칙은 이상하고 휴대가 불가능하며 모든 최신 쉘은$(foo) 은 직관적 인 인용 규칙을 제외하고는 절대적으로 동등한 을 제공합니다.

산술 대체의 출력도 동일한 확장을 거치지 만 확장 할 IFS수없는 문자 만 포함하므로 일반적으로 문제 가되지 않습니다 ( 숫자 또는를 포함하지 않는다고 가정 -).

큰 따옴표는 언제 필요합니까?를 참조하십시오 . 따옴표를 생략 할 수있는 경우에 대한 자세한 내용은

이 모든 rigmarole이 발생하는 것을 의미하지 않는 한, 변수 및 명령 대체 주위에 항상 큰 따옴표를 사용하는 것을 기억하십시오. 주의하세요. 따옴표를 생략하면 오류뿐만 아니라 보안 허점이 발생할 수 있습니다 .


7

다른 문제뿐만 아니라 인용 실패에 의해 발생 -n하고 -e소비 할 수있는 echo인수로. (전자 만 POSIX 사양에 따라 합법적 echo이지만 몇 가지 일반적인 구현은 사양을 위반하고 소비 -e합니다).

이를 방지하려면 세부 사항이 중요한 경우 대신 사용 하십시오.printfecho

그러므로:

$ vars="-e -n -a"
$ echo $vars      # breaks because -e and -n can be treated as arguments to echo
-a
$ echo "$vars"
-e -n -a

그러나 다음을 사용할 때 올바른 인용이 항상 당신을 구하는 것은 아닙니다 echo.

$ vars="-n"
$ echo $vars
$ ## not even an empty line was printed

... 그것은 반면 것이다 당신을 저장합니다 printf:

$ vars="-n"
$ printf '%s\n' "$vars"
-n

예, 우리는 이것을 위해 좋은 중복 제거가 필요합니다! 나는 이것이 질문 제목에 맞다는 데 동의하지만 여기에서 가치있는 가시성을 얻지 못할 것이라고 생각합니다. "내 -e/ -n/ 백 슬래시가 표시되지 않는 이유는 무엇 입니까?" 라는 새 질문은 어떻습니까? 여기에서 적절하게 링크를 추가 할 수 있습니다.
그 다른 사람

소비 -n 의미 했습니까 ?
PesaThe

1
@PesaThe, 아니, 나는 의미했다 -e. 의 표준 echo은 첫 번째 인수가이면 출력을 지정하지 않으므로이 경우 -n가능한 모든 출력을 합법적으로 만듭니다. 에 대한 그러한 조항은 없습니다 -e.
Charles Duffy

오 .. 읽을 수가 없어요. 내 영어를 비난합시다. 설명 해주셔서 감사합니다.
PesaThe

6

정확한 값을 얻으려면 사용자 큰 따옴표. 이렇게 :

echo "${var}"

그리고 그것은 당신의 가치를 정확하게 읽을 것입니다.


작품 .. 감사합니다
Tshilidzi Mudau

2

echo $var출력은 IFS변수 의 값에 크게 의존 합니다. 기본적으로 공백, 탭 및 개행 문자가 포함됩니다.

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
 ^I$

즉, 쉘이 필드 분할 (또는 단어 분할)을 수행 할 때 이러한 모든 문자를 단어 구분 기호로 사용합니다. 이것은 큰 따옴표없이 변수를 참조하여 에코 ($var )하여 예상 출력이 변경 될 .

단어 분할을 방지하는 한 가지 방법 (큰 따옴표 사용 외에)은 IFSnull 로 설정 하는 것입니다. http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_05 참조 :

IFS 값이 null이면 필드 분할이 수행되지 않습니다.

null로 설정하면 빈 값으로 설정됩니다.

IFS=

테스트:

[ks@localhost ~]$ echo -n "$IFS" | cat -vte
 ^I$
[ks@localhost ~]$ var=$'key\nvalue'
[ks@localhost ~]$ echo $var
key value
[ks@localhost ~]$ IFS=
[ks@localhost ~]$ echo $var
key
value
[ks@localhost ~]$ 

2
당신은 또한 set -fglobbing을 방지 해야 할 것입니다
그 다른 남자

@thatotherguy, 경로 확장이있는 첫 번째 예제에 정말 필요합니까? 으로 IFS널 (null)로 설정되어 echo $var확장 될 echo '/* Foobar is free software */'경로 확장은 작은 따옴표 문자열 안에 수행되지 않는다.
ks1322 2015

1
예. 당신이 경우 mkdir "/this thing called Foobar is free software etc/"당신은 여전히 확장 것을 볼 수 있습니다. [a-z]예를 들면 분명히 더 실용적입니다 .
그 다른 사람

[a-z]예 를 들어 이것은 의미가 있습니다.
ks1322

2

ks1322답변은 사용하는 동안 문제를 식별하는 데 도움이되었습니다.docker-compose exec .

-T플래그 를 생략하고 docker-compose exec출력을 중단하는 특수 문자를 추가하면 b대신 다음이 표시 됩니다 1b.

$ test=$(/usr/local/bin/docker-compose exec db bash -c "echo 1")
$ echo "${test}b"
b
echo "${test}" | cat -vte
1^M$

-T플래그를 사용하면 docker-compose exec예상대로 작동합니다.

$ test=$(/usr/local/bin/docker-compose exec -T db bash -c "echo 1")
$ echo "${test}b"
1b

-2

변수를 따옴표로 묶는 것 외에도 tr공백을 사용 하고 개행 문자로 변환 하여 변수의 출력을 변환 할 수도 있습니다 .

$ echo $var | tr " " "\n"
foo
bar
baz

이것은 조금 더 복잡하지만 배열 변수 사이의 구분 기호로 모든 문자를 대체 할 수 있으므로 출력에 더 많은 다양성을 추가합니다.


2
그러나 이것은 모든 공백을 줄 바꿈으로 대체 합니다 . 인용은 기존 줄 바꿈과 공백을 유지합니다.
user000001

맞아요. 나는 그것이 변수 안에 무엇이 있는지에 달려 있다고 생각합니다. 실제로 tr다른 방법으로 텍스트 파일에서 배열을 만듭니다.
Alek

3
변수를 적절하게 인용하지 않고 문제를 생성 한 다음 복잡한 추가 프로세스로 문제를 해결하는 것은 좋은 프로그래밍이 아닙니다.
tripleee

@Alek, ... err, 뭐? tr텍스트 파일에서 배열을 적절하게 / 올바르게 생성 할 필요가 없습니다 . IFS를 설정하여 원하는 구분 기호를 지정할 수 있습니다. 예를 들어 : IFS=$'\n' read -r -d '' -a arrayname < <(cat file.txt && printf '\0')bash 3.2 (광범위하게 유통되는 가장 오래된 버전)까지 거슬러 올라가며 cat실패한 경우 종료 상태를 false로 올바르게 설정합니다 . 새 줄 대신 탭을 원하면 $'\n'$'\t'.
Charles Duffy

1
당신이 좋아하는 일을하는 경우 @Alek, ... arrayname=( $( cat file | tr '\n' ' ' ) )의 여러 레이어에 깨진 것을 다음 : 그것은 (소위 결과를 글 로빙있어 *현재 디렉토리에있는 파일의 목록으로 회전), 그리고 그것은없이 단지 잘 작동tr ( 또는 cat, 그 문제에 대해; 하나는 사용할 수 arrayname=$( $(<file) )있으며 동일한 방식으로 손상되지만 덜 비효율적입니다).
Charles Duffy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.