파이프 라인에서 함수가 호출 될 때 환경 변수가 설정되지 않음


10

환경 변수를 설정하는 다음과 같은 재귀 함수가 있습니다.

function par_set {
  PAR=$1
  VAL=$2
  if [ "" != "$1" ]
  then
    export ${PAR}=${VAL}
    echo ${PAR}=${VAL}
    shift
    shift
    par_set $*
  fi
}

스스로 호출하면 변수를 설정하고 stdout으로 에코합니다.

$ par_set FN WORKS
FN=WORKS
$ echo "FN = "$FN
FN = WORKS

stdout을 파일로 리디렉션하면 작동합니다.

$ par_set REDIR WORKS > out
cat out
REDIR=WORKS
$ echo "REDIR = "$REDIR
REDIR = WORKS

그러나 stdout을 다른 명령으로 파이프하면 변수가 설정되지 않습니다.

$ par_set PIPE FAILS |sed -e's/FAILS/BARFS/'
PIPE=BARFS
$ echo "PIPE = "$PIPE
PIPE =

파이프가 함수가 변수를 내 보내지 못하게하는 이유는 무엇입니까? 임시 파일이나 명명 된 파이프에 의존하지 않고이 문제를 해결할 수있는 방법이 있습니까?

해결 :

Gilles 덕분에 작업 코드 :

par_set $(echo $*|tr '=' ' ') > >(sed -e's/^/  /' >> ${LOG})

이를 통해 스크립트를 다음과 같이 호출 할 수 있습니다.

$ . ./script.sh PROCESS_SUB ROCKS PIPELINES=NOGOOD
$ echo $PROCESS_SUB
ROCKS
$ echo $PIPELINES
NOGOOD
$ cat log
7:20140606155622162731431:script.sh:29581:Parse Command Line parameters.  Params must be in matched pairs separated by one or more '=' or ' '.
  PROCESS_SUB=ROCKS
  PIPELINES=NOGOOD

전체 코드에 관심이있는 경우 비트 버킷 https://bitbucket.org/adalby/monitor-bash 에서 호스팅되는 프로젝트 .

답변:


8

파이프 라인의 각 부분 (즉, 파이프의 각 측면)은 별도의 프로세스 (쉘 이 스크립트의 일부를 실행하기 위해 서브 프로세스를 포크 할 때 서브 쉘이라고 함)에서 실행됩니다 . 에서 par_set PIPE FAILS |sed -e's/FAILS/BARFS/'PIPE변수는 파이프의 좌측을 실행하는 서브 프로세스에서 설정된다. 이 변경 사항은 상위 프로세스에 반영되지 않습니다 (환경 변수는 프로세스간에 전송되지 않으며 하위 프로세스에 의해서만 상속됩니다).

파이프의 왼쪽은 항상 서브 쉘에서 실행됩니다. 일부 쉘 (ATT ksh, zsh)은 상위 쉘에서 오른쪽을 실행합니다. 대부분은 서브 쉘에서 오른쪽을 실행합니다.

스크립트의 일부 출력을 재지 정하고 ksh / bash / zsh의 상위 쉘에서 해당 부분을 실행하려면 프로세스 대체를 사용할 수 있습니다 .

par_set PROCESS SUBSTITUTION > >(sed s/ION/ED/)

POSIX 셸을 사용하면 출력을 명명 된 파이프로 리디렉션 할 수 있습니다.

mkfifo f
<f grep NAMED= &
par_set NAMED PIPE >f

아, 그리고 변수 대체 에 대한 따옴표 가 누락되었습니다 par_set name 'value with spaces' star '*'. 코드는 다음과 같습니다 .

export "${PAR}=${VAL}"

par_set "$@"

승리를위한 프로세스 대체! 명명 된 파이프 또는 임시 파일을 사용할 수 있다는 것을 알았지 만 추악하고 동시성이 떨어지며 스크립트가 죽으면 엉망이됩니다 (트랩이 마지막 파일에 도움이 됨). 공간적인 것은 의도적입니다. 일반적으로 명령 행에 전달 된 변수는 이름 / 값 쌍에 있으며 '='및 / 또는 ''로 구분됩니다.
Andrew

3

파이프의 각면이의 서브 쉘에서 실행되고 서브 쉘에 bash설정된 변수가 해당 서브 쉘에 로컬 이기 때문에 작동하지 않습니다 .

최신 정보:

부모에서 자식 셸로 변수를 전달하는 것은 쉽지만 실제로 는 다른 방법으로 수행하는 것이 어렵습니다. 일부 해결 방법은 파이프, 임시 파일, stdout에 쓰기 및 상위에서 읽기 등입니다.

일부 참고 문헌 :

http://mywiki.wooledge.org/BashFAQ/024
https://stackoverflow.com/q/15541321/3565972
https://stackoverflow.com/a/15383353/3565972
http://forums.opensuse.org/showthread .php / 458979- 어떻게 내보내기-변수-서브 쉘-백-아웃-부모


가능성이 있다고 생각했지만 현재 쉘에서 함수를 실행해야합니다. 함수에 "echo $$"를 추가하여 테스트했습니다. par_set STILL FAILS | sed -e "s / ^ / sedpid = $$, fnpid = /"출력 sedpid = 15957, fnpid = 15957.
Andrew

@Andrew이 stackoverflow.com/a/20726041/3565972를 참조하십시오 . 분명히 $$부모와 자식 껍질 모두 동일합니다. $BASHPID서브 쉘 pid를 얻는 데 사용할 수 있습니다 . 내가 echo $$ $BASHPID안에 있을 때 나는 par_set다른 pid를 얻는다.
savanto

@Andrew 여전히 해결 방법을 찾으려고하지만 실패합니다! =)
savanto

@Savanto 감사합니다. 나는 $$ vs $ BASHPID 또는 파이프 강제 서브 쉘에 대해 몰랐습니다.
Andrew

0

파이프 라인 외부의 쉘에서 약간의 문제가 발생할 수있는 서브 쉘을 지적하지만 문제의 더 어려운 부분은 파이프 라인의 동시성과 관련이 있습니다.

파이프 라인의 모든 프로세스 멤버는 한 번에 시작 하므로 다음과 같이 보면 문제를 이해하기가 더 쉬울 수 있습니다.

{ sleep 1 ; echo $((f=1+2)) >&2 ; } | echo $((f))
###OUTPUT
0
...
3

파이프 라인 프로세스는 변수 값을 설정하기 전에 변수 값이 이미 꺼져 있고 실행 중이므로 변수 값을 상속 할 수 없습니다.

나는 당신의 기능의 요점이 무엇인지 이해할 수 없습니다- export아직 어떤 목적을 달성 하지 못합니까? 아니면 그냥 var=val? 예를 들어 다음은 거의 동일한 파이프 라인입니다.

pipeline() { 
    { sleep 1
      echo "f=$((f=f+1+2))" >&3
    } | echo $((f)) >&2
} 3>&1

f=4 pipeline

###OUTPUT

4
...
f=7

그리고 export:

export $(f=4 pipeline) ; pipeline

###OUTPUT:

4
7
...
f=10

그래서 당신의 일은 다음과 같이 작동 할 수 있습니다 :

par_set $(echo PIPE FAILS | 
    sed 's/FAIL/WORK/;/./w /path/to/log')

파일 sed의 출력에 기록하여 쉘 분할로 함수에 전달합니다 "$@".

또는 대안으로 :

$ export $(echo PIPE FAILS | sed 's/ FAIL/=WORK/')
$ par_set $PIPE TWICE_REMOVED
$ echo "WORKS = "$WORKS
WORKS = TWICE_REMOVED

그래도 함수를 작성하려고하면 아마도 다음과 같이 보일 것입니다.

_env_eval() { 
    while ${2+:} false ; do
       ${1:+export} ${1%%["${IFS}"]*}="${2}" || :
       shift 2
    done
}

그것은 사실이지만 관련이 없습니다. Andrew는 파이프의 다른 쪽이 아니라 파이프 라인이 끝난 후에 변수를 사용하려고합니다.
Gilles 'SO- 악마 그만'

@Gilles-그는 그렇게 할 수 있습니다. |pipeline | sh
mikeserv 2016

@Gilles-실제로는 필요하지 않습니다 sh. 그는 이미을 사용하고 export있습니다.
mikeserv

전반적인 목표는 시스템 모니터링 스크립트입니다. 대화식, 다른 스크립트 또는 cron에서 호출 할 수 있기를 원합니다. env vars 설정, 명령 행 또는 구성 파일 전달을 통해 매개 변수를 전달할 수 있기를 원합니다. 하나 이상의 소스에서 입력을 받고 환경을 올바르게 설정하는 기능이 있습니다. 그러나 sed를 통해 포맷 한 후 출력을 선택적으로 기록 할 수 있기를 원하므로 파이프해야합니다.
앤드류

1
모든 의견을 보내 주셔서 감사합니다. 프로세스 대체에 대한 Gilles 제안과 함께 갔지만 내 코드를 주장해야하면 더 나은 프로그래머가됩니다.
앤드류
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.