서브쉘을 사용하지 않고 쉘 명령 대체를 수행할 수 있습니까?

서브쉘을 사용하지 않고 쉘 명령 대체를 수행할 수 있습니까?

서브셸을 사용하지 않고 명령 대체가 필요한 시나리오가 있습니다. 나는 다음과 같은 구조를 가지고 있습니다 :

pushd $(mktemp -d)

이제 임시 디렉토리를 한 번에 종료하고 삭제하고 싶습니다.

rmdir $(popd)

popd그러나 이는 팝된 디렉터리가 반환되지 않고(새 현재 디렉터리를 반환함) 하위 쉘에서 실행되기 때문에 작동하지 않습니다 .

그것은 마치

dirs -l -1 ; popd &> /dev/null

팝된 디렉토리를 반환하지만 다음과 같이 사용할 수는 없습니다.

rmdir $(dirs -l -1 ; popd &> /dev/null)

popd서브쉘에만 영향을 미치기 때문입니다 . 필요한 것은 이를 수행하는 능력입니다:

rmdir { dirs -l -1 ; popd &> /dev/null; }

그러나 이는 잘못된 구문입니다. 그러한 효과를 얻을 수 있습니까?

(참고: 임시 디렉터리를 변수에 저장할 수 있다는 것을 알고 있습니다. 그렇게 하지 않고 그 과정에서 새로운 것을 배우려고 노력하고 있습니다!)

답변1

귀하의 질문에 대한 제목 선택이 약간 혼란스럽습니다.

pushd/는 복사 기능 popd이며 기억된 디렉토리들을 관리하는 방법입니다.cshbashzsh

pushd /some/dir

홍보하다현재의작업 디렉터리를 스택에 추가하고그 다음에현재 작업 디렉터리를 변경한 다음 /some/dir해당 스택의 내용을 인쇄합니다(공백으로 구분).

popd

스택의 내용을 다시 공백으로 구분하여 인쇄한 다음 스택의 최상위 요소로 변경하고 스택에서 팝합니다.

~/x(또한 일부 디렉토리는 또는 기호로 표시됩니다 ~user/x.)

따라서 스택에 현재 디렉터리가 있고 /a현재 /b디렉터리가 있고 /here실행 중인 경우:

 pushd /tmp/whatever
 popd

pushd대신 인쇄 /tmp/whatever /here /a /b하고 popd출력 합니다 . 이는 명령 대체 사용 여부와 관계가 없습니다. 이전 디렉토리에 대한 경로를 얻는 데 사용할 수 없으며 해당 출력은 일반적으로 사후 처리될 수 없습니다(디렉토리 스택의 요소에 액세스하려면 일부 쉘 또는 배열을 참조하십시오)./here /a /b/tmp/whateverpopd$dirstack$DIRSTACK

아마도 당신은 다음을 원할 것입니다:

pushd "$(mktemp -d)" &&
popd &&
rmdir "$OLDPWD"

또는

cd "$(mktemp -d)" &&
cd - &&
rmdir "$OLDPWD"

그러나 나는 다음을 사용합니다:

tmpdir=$(mktemp -d) || exit
(
  cd "$tmpdir" || exit # in a subshell 
  # do what you have to do in that tmpdir
)
rmdir "$tmpdir"

어떤 경우에도 하위 쉘에서는 실행 pushd "$(mktemp -d)"되지 않습니다 . pushd그렇다면 작업 디렉터리를 변경할 수 없습니다. 이는 mktemp서브 쉘에서 실행됩니다. 별도의 명령이므로 별도의 프로세스에서 실행해야 합니다. 출력을 파이프에 쓰고, 쉘 프로세스는 파이프의 다른 쪽 끝에서 이를 읽습니다.

ksh93은 명령이 내장될 때 별도의 프로세스를 피할 수 있지만 거기에도 여전히 하위 쉘(다른 작업 환경)이 있으며 이번에는 일반적으로 포크에서 제공되는 별도의 환경에 의존하지 않고 에뮬레이트됩니다. 예를 들어 에서는 ksh93분기 a=0; echo "$(a=1; echo test)"; echo "$a"가 포함되지 않지만 출력은 여전히 echo "$a"​​존재합니다 0.

mktemp여기에서 의 출력을 에 전달하는 동안 변수에 저장하려면 다음을 수행하면 됩니다 pushd.zsh

pushd ${tmpdir::="$(mktemp -d)"}

다른 Bourne과 유사한 쉘의 경우:

unset -v tmpdir
pushd "${tmpdir=$(mktemp -d)}"

또는 출력을 $(mktemp -d)변수에 명시적으로 저장하지 않고 여러 번 사용하려면 zsh익명 함수를 사용할 수 있습니다.

(){pushd ${1?} && cd - && rmdir $1} "$(mktemp -d)"

답변2

디렉터리를 떠나기 전에 디렉터리 연결을 해제할 수 있습니다.

rmdir "$(pwd -P)" && popd

또는

rmdir "$(pwd -P)" && cd ..   # yes, .. is still usable

그러나 pushd와 는 popd실제로 스크립트가 아닌 대화형 셸을 위한 도구입니다(이것이 너무 수다스러운 이유입니다. 실제 스크립트 명령은 성공 시 침묵합니다).

답변3

비슷한 문제가 발생하여 여기에 솔루션을 게시하겠습니다. 분명히 사용할 때 서브쉘 확장을 피할 수 있는 방법이 없으므로 $(command)제가 얻은 유일한 해결책은 전역 변수를 사용하고 이를 함수에 전달하고 eval. 제 상황은 좀 까다롭습니다. 함수에서 파일 설명자를 열고 fd 번호를 다시 기본 함수에 전달하고 싶습니다. 문제는 파일 설명자가 기본 셸이 아닌 하위 셸에서 열린다는 것입니다.


open_fd_and_return() {
    local _my_file="${1}" _my_fd_no

    exec {_my_fd_no}>"${_my_file}" || die "impossible to open the fd"
    echo "${_my_fd_no}"
}
my_file="$(mktemp --dry-run)"
my_fd="$(open_fd_and_return "${my_file}")"
echo "test $$" >&"${my_fd}"
rm "${my_file}"
exec {my_fd}>&-

그러면 "잘못된 파일 설명자 오류"가 반환됩니다.

내 현재 솔루션은 eval을 사용하는 것입니다(더 추악하더라도).

open_fd_and_return_eval() {
    local _my_var="${1}"
    local _my_file="${2}"
    local _my_fd_no

    exec {_my_fd_no}>"${_my_file}" || die "impossible to open the fd"
    eval "${_my_var}=\${_my_fd_no}"
}

my_file="$(mktemp --dry-run)"
my_fd=""
open_fd_and_return_eval "my_fd" "${1}"
echo "test $$" >&"${my_fd}"
rm -- "${my_file}"
exec {my_fd}>&-

이것은 작동합니다. 이것이 누군가에게 유용하기를 바랍니다.

답변4

일반적인 답변

서브쉘을 사용하지 않고 쉘 명령 대체를 수행할 수 있습니까?

문제, 명령 대체가 fish서브쉘에서 실행되지 않습니다.

$ set dir (cd /etc; and pwd)
$ echo -- $dir $PWD
/etc /etc

현재 작업 디렉토리를 변경하고 dir.

ksh93 및 최신 버전의 mksh도 ${ cmd; }하위 쉘에서 실행되지 않는 명령 대체 형식을 지원합니다.

$ dir=${ cd /etc && pwd; }
$ print -r -- "$dir" "$PWD"
/etc /etc

mksh이를 위해 임시 파일을 사용하십시오.

다른 쉘의 경우 언제든지 임시 파일을 사용할 수 있습니다.

cmd > "$tempfile"
output=$(cat<"$tempfile")

관련 정보