Bash에서 명령 출력을 변수로 읽는 더 효율적이거나 권장되는 방법은 무엇입니까?

Bash에서 명령 출력을 변수로 읽는 더 효율적이거나 권장되는 방법은 무엇입니까?

시스템 명령의 한 줄 출력을 Bash 셸 변수로 읽으려면 다음 예에 표시된 것처럼 최소한 두 가지 옵션이 있습니다.

  1. IFS=: read user x1 uid gid x2 home shell <<<$(grep :root: /etc/passwd | head -n1)

그리고

  1. IFS=: read user x1 uid gid x2 home shell < <(grep :root: /etc/passwd | head -n1)

둘 사이에 차이가 있나요? 무엇이 더 효과적이거나 권장됩니까?


/etc/passwd문서는 예시용으로만 읽혀졌습니다. 내 질문의 초점은여기 문자열이 있습니다그리고프로세스 교체.

답변1

먼저, readWithout를 사용하는 것은 필드나 줄 구분 기호를 이스케이프하는 데 사용되는 -r입력을 처리하기 위한 것이지만 \, 그렇지 않습니다 /etc/passwd. 당신은 read그것을 사용하지 않으려는 경우가 거의 없습니다 -r.

이제 이 두 형식 모두 표준 구문이 아니라는 점에 유의하세요 sh. 1991년 <<<부터 zsh. 1985년경 <(...)부터 처음에는 리디렉션이 지원되지 않았습니다.kshksh

$(...)또한 ksh에서 왔지만 POSIX에 의해 표준화되었으므로( `...`잘못 설계된 Bourne 쉘을 대체했기 때문에) sh이제 구현 전반에 걸쳐 이식 가능합니다.

$(code)하위 셸의 코드가 해석되고 출력이 파이프로 리디렉션되는 반면, 상위 셸은 파이프의 다른 쪽 끝에서 출력을 읽고 이를 메모리에 저장합니다. 그런 다음 명령이 완료되면 출력에서 ​​후행 개행 문자가 제거되고(및 NUL 문자가 제거됨 bash) 확장이 형성됩니다 $(...).

참조되지 않고 목록 컨텍스트에 있는 경우 $(...)분할+glob(zsh에서만 분할)의 영향을 받습니다. 그 이후에는 <<<목록 컨텍스트가 아니지만 여전히이전 버전에서는 bash여전히 부분을 분할(전역이 아님)한 다음 해당 부분을 공백으로 연결합니다.. 그러므로 사용한다면 대상으로 사용시 인용 bash도 하시면 될 것 같습니다 .$(...)<<<

cmd <<< wordzsh 및 이전 버전의 bash에서 쉘은 word개행 문자와 개행 문자를 임시 파일에 저장한 다음 실행하려는 프로세스의 표준 입력으로 사용되어 cmd이전에 삭제된 임시 파일을 실행합니다. cmd이는 << EOF1970년대 Bourne Shell에서 발생한 것과 동일한 일입니다. 실제로는 다음과 정확히 동일합니다.

cmd << EOF
word
EOF

5.1에서 bash는 단어가 파이프 버퍼에 완전히 들어갈 수 있을 때마다 임시 파일 사용에서 파이프 사용으로 전환했으며(또는 교착 상태를 방지하지 않으려면 임시 파일 사용으로 대체) cmd쉘의 표준 입력에 of 파이프라인이 미리 시드되도록 했습니다 word.

따라서 cmd1 <<< "$(cmd2)"하나 또는 두 개의 파이프가 관련되어 전체 출력을 cmd2메모리에 저장하고 다시 다른 파이프 또는 임시 파일에 저장하고 NUL 및 개행 문자를 삭제합니다.

cmd1 < <(cmd2)cmd2 | cmd1에 해당하는 기능 의 출력은 cmd2파이프의 쓰기 끝에 연결됩니다. 그런 다음 <(...)다른 쪽 끝을 식별하는 경로로 확장되어 < that-path해당 다른 쪽 끝에 대한 파일 설명자를 제공합니다. 따라서 쉘이 데이터에 대해 어떠한 작업도 수행하지 않고 cmd2직접 대화를 수행할 수 있습니다 .cmd1

bash특히 bashAT&T ksh 또는 zsh와 달리 다음과 같은 이유로 셸에서 이 구성을 볼 수 있습니다 .

cmd2 | cmd1

cmd1서브셸에서 실행되므로 그렇다면 cmd1해당 서브셸에 대한 변수 readread채워집니다.

따라서 여기서는 다음을 원할 것입니다.

IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored < <(
  grep :root: /etc/passwd)

head중복과 마찬가지로 -r어쨌든 한 줄만 읽혀집니다² read. rest_if_any_ignored미래에 새로운 필드가 추가되어 추가 콘텐츠가 포함될 경우 /etc/passwd를 대비해 향후 교정을 위해 하나를 추가했습니다 .$shell/bin/sh:that-field

휴대용( sh)에서는 다음을 수행할 수 없습니다.

grep :root: /etc/passwd |
  IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored 

readPOSIX는 서브셸(예: bash/ dash...)에서 실행할지 또는 서브셸에서 실행하지 않을지(예: zsh/ ksh) 지정하지 않기 때문입니다 .

하지만 다음과 같이 할 수 있습니다.

IFS=: read -r user x1 uid gid x2 home shell rest_if_any_ignored << EOF
$(grep :root: /etc/passwd | head -n1)
EOF

(이는 head전체 grep출력이 메모리 및 임시 파일/파이프에 저장되는 것을 방지하기 위해 복원됩니다.)

이는 효율적이지 않더라도 표준입니다(@muru가 지적했듯이 이러한 작은 입력의 차이는 포크된 프로세스에서 외부 유틸리티를 실행하는 비용에 비해 무시할 수 있을 정도입니다).

여기서 성능이 중요한 경우 grep셸의 내장 기능을 사용하여 작업을 완료함으로써 성능을 향상시킬 수 있습니다. 그러나 특히 에서는 bash매우 작은 입력에 대해서만 이 작업을 수행할 수 있습니다. 셸은 이러한 작업을 위해 설계되지 않았고 이 점에서 에서보다 훨씬 작기 때문입니다 grep.

while
  IFS=: read <&3 -r user x1 uid gid name home shell rest_if_any_ignored
do
  if [ "$name" = root ]; then
    do-something-with "$user" "$home"...
    break
  fi
done 3< /etc/passwd

lastpipe¹ 옵션이 설정되어 있지 bash않고 쉘이 스크립트에서처럼 비대화형인 경우는 제외

-m1² 첫 번째 일치 후 검색을 중지하도록 지시하는 GNU 구현 또는 옵션도 참조하세요 . 또는 이식 가능한 것과 동등한 것:--max-count=1grepgrepsed '/:root:/!d;q'

답변2

존재하다여기에 있는 문자열, bash는 여기서 문자열의 내용을 생성하기 위해 replacement() 명령의 전체 출력을 읽은 $(grep :root: /etc/passwd | head -n1)다음 를 사용하여 다시 읽도록 지시합니다 read.

반면에,프로세스 교체, bash는 파이프를 설정한 다음 출력을 읽습니다.한 번.


한 줄을 읽기 위해 bash(및 다른 두 개의 외부 명령)를 실행 중입니다. 그 무렵에는 효율성이 사라진 지 오래되었습니다.


우리가 아직 그 일을 하고 있는 동안,암소 비슷한 일종의 영양grep옵션이 있습니다 -m:

-m 일련번호
--max-count=일련번호

첫 번째 이후에 중지일련번호선택된 라인.

관련 정보