bash 스크립트에서 / dev / stdout 대상 위치를 저장하는 방법은 무엇입니까?


12

/dev/stdout첫 번째 파일 설명자를 다른 위치로 바꾸기 전에 원래 위치 를 유지하려는 특정 bash 스크립트가 있습니다.

그래서 저는 자연스럽게 다음과 같이 썼습니다

old_stdout=$(readlink -f /dev/stdout)

그리고 그것은 작동하지 않았습니다. 문제가 무엇인지 매우 빨리 이해합니다.

test@ubuntu:~$ echo $(readlink -f /dev/stdout)
/proc/5175/fd/pipe:[31764]
test@ubuntu:~$ readlink -f /dev/stdout
/dev/pts/18

분명히, $()부모 쉘로 파이프되는 서브 쉘에서 실행됩니다.

문제는 /dev/stdoutbash 스크립트에서 위치를 문자열 로 저장하는 신뢰할 수있는 (Linux 배포판 간의 이식성 범위) 방법이 있습니까?


이것은 XY 문제 처럼 들립니다 . 근본적인 문제는 무엇입니까?
— Kusalananda

기본 문제는 자동으로 두 가지 모드로 실행되는 특정 설치 스크립트입니다. 자동 모드는 모든 출력을 파일에 기록하고 상세하게 표시하며 파일에 기록 할뿐만 아니라 모든 것을 터미널에 인쇄합니다. 그러나 두 모드에서 스크립트는 사용자와 상호 작용하기를 원합니다. 즉, 터미널로 인쇄하고 사용자 응답을 읽습니다. 따라서 저장 /dev/stdout하면 자동 모드에서 메시지를 인쇄 할 때 발생하는 문제를 해결할 것이라고 생각했습니다 . 대안은 출력을 생성하는 다른 모든 작업을 리디렉션하는 것입니다. 사용자 상호 작용 메시지보다 약 100 배 더 많습니다.
— alexey.e.egorov

사용자와 상호 작용하는 표준 방법은에 인쇄하는 것 stderr입니다. 예를 들어, 프롬프트가 stderr기본적으로 표시되는 이유 입니다.
— Kusalananda

불행히도, stderr스크립트는 많은 외부 프로그램을 호출하고 가능한 모든 오류 메시지를 수집하고 기록하기 때문에 수정하고 저장해야합니다.
— alexey.e.egorov

답변:


14

파일 디스크립터를 저장하려면 다른 fd에 파일 디스크립터를 복제하십시오. 해당 파일의 경로를 저장하는 것만으로는 충분하지 않습니다. 시작 모드, 시작 플래그, 파일 내 현재 위치 등을 저장해야합니다. 물론 익명의 파이프 또는 소켓의 경우 경로가 없으므로 작동하지 않습니다. 저장하려는 것은 fd가 참조 하는 열린 파일 설명 이며, fd를 복제하면 실제로 동일한 fd 파일 설명 으로 새 fd를 리턴합니다 .

Bourne과 같은 쉘을 사용하여 파일 디스크립터를 다른 파일 디스크립터에 복제하는 구문은 다음과 같습니다.

exec 3>&1

위의 fd 1은 fd 3에 복제됩니다.

fd 3이 이미 열려 있던 것은 닫히지 만 fd 3 ~ 9 (일반적으로 최대 99까지 yash)는 해당 목적을 위해 예약되어 있으며 0, 1 또는 2와 반대되는 특별한 의미는 없습니다. 쉘은 자체 내부 비즈니스에 사용하지 않는 것을 알고 있습니다. fd 3가 미리 열린 유일한 이유는 스크립트 1 에서 실행했거나 호출자가 유출 했기 때문 입니다.

그런 다음 stdout을 다른 것으로 변경할 수 있습니다.

exec > /dev/null

나중에 stdout을 복원하려면 다음을 수행하십시오.

exec >&3 3>&-

( 3>&-더 이상 필요없는 파일 디스크립터를 닫는 중).

이제 문제는 ksh를 제외하고 그 이후에 실행하는 모든 명령 exec 3>&1이 fd 3을 상속한다는 것입니다. 그것은 fd 누출입니다. 일반적으로 큰 문제는 아니지만 문제가 발생할 수 있습니다.

ksh세트 근접에-간부 (2 이상 FDS를 위해) 그 FDS에 플래그를하지만, 다른하지 쉘과 다른 쉘은 수동으로 세트에 그 깃발을 어떤 방법이 없습니다.

다른 쉘의 해결 방법은 다음과 같이 각 명령마다 fd 3을 닫는 것입니다.

exec 3>&-

exec > file.log

ls 3>&-
uname 3>&-

exec >&3 3>&-

번거로운. 여기서 가장 좋은 방법은 전혀 사용하지 않고 exec명령 그룹을 리디렉션하는 것입니다.

{
  ls
  uname
} > file.log

, 거기는 표준 출력 저장하고 나중에 복원 할을 담당 쉘은 (그리고 그것은 99 위, 9 위 (A (FD) 상을 복제하여 내부적으로합니까 yash과) 가까운 온 간부 플래그가 설정).

참고 1

이제 fd 3-9를 광범위하게 또는 기능적으로 사용하는 경우, 특히 스크립트에서 해당 fd를 사용할 수있는 타사 코드를 사용하는 경우 이러한 fd 3-9를 관리하는 것은 번거롭고 문제가 될 수 있습니다.

일부 쉘 ( zsh, bash, ksh93, 모든 기능 (추가 의 올리버 고기 잡이 통발에 의해 제안zsh 이 경우에 도움이 대신 10 위의 첫 번째 무료 FD를 할당 할 수있는 대안 구문이 그들의 개발자들 사이에서 논의 된 후 2005 년 같은시기를)) :

myfunction() {
  local fd
  exec {fd}>&1
  # stdout was duplicated onto a new fd above 10, whose actual value
  # is stored in the fd variable
  ...
  # it should even be safe to re-enter the function here
  ...
  exec >&"$fd" {fd}>&-
}

또한 rc.local서비스 에서 스크립이 실행될 때 fd 3이 이미 사용되었을 수 있다는 의미에서 코드가 잘못되었습니다 exec {FD}>&1. 그러나 이것은 bash 4에서만 지원되며 이는 매우 슬픈 일입니다. 따라서 이것은 실제로 이식성이 없습니다.
— alexey.e.egorov

@ alexey.e.egorov, 편집 참조.
— Stéphane Chazelas

Bash 3. *는이 기능을 지원하지 않으며이 버전은 Centos 5에서 사용되며 여전히 지원되며 여전히 사용됩니다. 그리고 무료 디스크립터를 찾는 eval "exec $i>&1"것은 번거롭기 때문에 피하고 싶은 것입니다. 그러면 9보다 큰 fd가 자유로울 수 있다는 것을 정말로 믿을 수 있습니까 ?
— alexey.e.egorov

@ alexey.e.egorov, 아니, 당신은 그것을 뒤로보고 있습니다. fds 3 ~ 9는 무료로 사용할 수 있으며 원하는대로 관리 할 수 ​​있습니다. 9 이상의 fd는 내부적으로 쉘에서 사용될 수 있으며 닫으면 불쾌한 결과를 초래할 수 있습니다. 대부분의 껍질은 당신이 그들을 사용할 수 없습니다. bash발로 자신을 쏠 수 있습니다.
— Stéphane Chazelas

2
@ alexey.e.egorov, 시작시 스크립트에 (3..9)에 fds가 열려 있으면 호출자가 닫거나 실행중인 플래그를 잊어 버렸기 때문입니다. 그것이 내가 fd 누출이라고 부르는 것입니다. 이제 발신자가 해당 fd를 사용자에게 전달하려고했기 때문에 데이터를 읽거나 쓸 수는 있지만 알고있을 것입니다. 당신이 그들에 대해 모른다면, 당신은 상관하지 않으며, 당신은 그것들을 자유롭게 닫을 수 있습니다 (발신자의 프로세스가 아닌 스크립트의 프로세스 fd를 닫습니다).
— Stéphane Chazelas

3

보시다시피, bash 스크립팅은 파일 디스크립터를 할당 할 수있는 일반 프로그래밍 언어와 다릅니다.

가장 간단한 해결책은 서브 쉘을 사용하여 경로 재 지정하려는 항목을 실행하여 표준 I / O가있는 최상위 쉘로 처리를 되돌릴 수 있도록하는 것입니다.

대체 솔루션은 ttyTTY 장치를 식별하고 스크립트에서 I / O를 제어하는 ​​데 사용 하는 것 입니다. 예를 들면 다음과 같습니다.

dev=$(tty)

그리고 당신은 할 수 있습니다 ..

echo message > $dev

> 대체 솔루션은 tty를 사용하여 TTY 장치를 식별하고 스크립트에서 I / O를 제어하는 ​​것입니다. 어떻게합니까?
— alexey.e.egorov

1
방금 답변에 예를 포함 시켰습니다.
— Julie Pelletier

1

$$ 대화식 쉘 또는 스크립트 관련 쉘 PID의 경우 현재 프로세스 PID를 얻을 수 있습니다.

따라서 다음을 사용할 수 있습니다.

readlink -f /proc/$$/fd/1

예:

% readlink -f /proc/$$/fd/1
/dev/pts/33

% var=$(readlink -f /proc/$$/fd/1)

% echo $var                       
/dev/pts/33

1
기능적이지만 특정 /proc구조 에 의존 /dev/stdout하면 질문에서 언급 한대로 사용하는 것처럼 이식성 문제가 발생합니다 .
— Julie Pelletier

1
@JuliePelletier 특정 /proc구조 에 의존하고 있습니까? 그것은 어떤 리눅스에서도 작동합니다 procfs..
— heemayl

1
그렇기 때문에 procfs거의 항상 존재하는 것처럼 Linux를 일반화 할 수 있지만 이식성에 대한 질문이 종종 있으며 다른 시스템으로의 이식성을 고려하는 좋은 개발 방법론이 있습니다. bash다양한 운영 체제에서 실행할 수 있습니다.
— Julie Pelletier
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.