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.