상태 저장 bash 기능

상태 저장 bash 기능

각 호출마다 카운트를 증가(및 반환)하는 함수를 Bash에 구현하고 싶습니다. 불행하게도 이것은 하위 쉘 내에서 함수를 호출하므로 상위 쉘의 변수를 수정할 수 없기 때문에 이것이 사소한 것 같지 않습니다.

내 시도는 다음과 같습니다.

PS_COUNT=0

ps_count_inc() {
    let PS_COUNT=PS_COUNT+1
    echo $PS_COUNT
}

ps_count_reset() {
    let PS_COUNT=0
}

이는 다음과 같이 사용됩니다(따라서 서브셸에서 함수를 호출해야 합니다).

PS1='$(ps_count_reset)> '
PS2='$(ps_count_inc)   '

이렇게 하면 번호가 매겨진 여러 줄 프롬프트가 표시됩니다.

> echo 'this
1   is
2   a
3   test'

사랑스러운. 하지만 위의 제한 사항으로 인해 작동하지 않습니다.

작동하지 않는 솔루션은 변수 대신 파일에 개수를 쓰는 것입니다. 그러나 이로 인해 동시에 실행되는 여러 세션 간에 충돌이 발생할 수 있습니다. 물론 파일 이름에 셸의 프로세스 ID를 추가할 수도 있습니다. 하지만 나는 수많은 파일로 인해 내 시스템이 복잡해지지 않는 더 나은 솔루션을 원합니다.

답변1

여기에 이미지 설명을 입력하세요.

질문에서 확인한 것과 동일한 결과를 얻으려면 다음을 수행하면 됩니다.

PS1='${PS2c##*[$((PS2c=0))-9]}- > '
PS2='$((PS2c=PS2c+1)) > '

비틀 필요가 없습니다. 이 두 줄의 코드는 POSIX 호환성에 가까운 척하는 모든 셸에서 이 모든 작업을 수행합니다.

- > cat <<HD
1 >     line 1
2 >     line $((PS2c-1))
3 > HD
    line 1
    line 2
- > echo $PS2c
0

하지만 나는 이것을 좋아한다. 저는 이 작업을 더 좋게 만드는 기본 사항을 보여주고 싶습니다. 그래서 이것을 조금 편집했습니다. 지금은 넣어 두겠지만 /tmp, 나 자신을 위해서도 보관할 것 같아요. 여기있어:

cat /tmp/prompt

프롬프트 스크립트:

ps1() { IFS=/
    set -- ${PWD%"${last=${PWD##/*/}}"}
    printf "${1+%c/}" "$@" 
    printf "$last > "
}

PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'
PS2='$((PS2c=PS2c+1)) > '

참고: 최근에 배웠습니다.야쉬, 어제 만들었어요. 어떤 이유로든 문자열을 사용하여 각 인수의 첫 번째 바이트를 인쇄하지 않습니다 %c. 문서는 해당 형식의 와이드 문자 확장에 특정하므로 관련이 있을 수 있습니다.%.1s

그게 다야. 거기서는 크게 두 가지 일이 일어났습니다. 다음과 같습니다.

/u/s/m/man3 > cat <<HERE
1 >     line 1
2 >     line 2
3 >     line $((PS2c-1))
4 > HERE
    line 1
    line 2
    line 3
/u/s/m/man3 >

분석하다$PWD

평가될 때 마다 $PS1구문 분석 및 인쇄되어 $PWD프롬프트에 추가됩니다. 하지만 전체 화면이 혼잡해지는 것을 좋아하지 않기 $PWD때문에 현재 디렉토리의 현재 경로에 있는 각 탐색경로의 첫 글자만 보고 전체 디렉토리를 보고 싶습니다. 이와 같이:

/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cd /
/ > cd ~
/h/mikeserv > 

다음은 몇 가지 단계입니다.

IFS=/

우리는 현재를 분할해야 하며 $PWD가장 신뢰할 수 있는 방법은 $IFS분할을 사용하는 것입니다 /. 그 후에는 전혀 신경 쓸 필요가 없습니다. 여기서부터 모든 분할은 $@다음 명령에서 쉘의 위치 인수 배열에 의해 정의됩니다. 예를 들면 다음과 같습니다.

set -- ${PWD%"${last=${PWD##/*/}}"}

그래서 이것은 약간 까다롭지만 가장 중요한 것은 $PWD기호에 따라 나뉘어 있다는 것입니다. 또한 매개변수 확장을 사용하여 가장 왼쪽 슬래시와 가장 오른쪽 슬래시 사이에 나타나는 값 뒤에 모든 것을 /할당합니다 . 그렇게 하면 내가 방금 들어갔는데 하나만 있으면 여전히 전체와 동일 하고 비어 있다는 것을 알 수 있습니다 . 이건 중요하다. 또한 에 할당하기 전에 의 꼬리에서 제거합니다 .$last///$last$PWD$1$last$PWD$@

printf "${1+%c/}" "$@"

${1+is set}따라서 여기서는 각 셸 매개변수의 printf첫 번째 문자(현재 디렉토리의 모든 디렉토리 (최상위 디렉토리 제외) %c에 설정 )를 분할할 수 있는 한 입니다 . 따라서 우리는 기본적으로 최상위 디렉토리를 제외한 모든 디렉토리의 첫 번째 문자를 인쇄합니다. 그러나 이는 설정될 때만 발생 하며 루트 나 .$PWD/$PWD$1///etc

printf "$last > "

$last방금 최상위 디렉토리에 할당한 변수입니다. 이제 이것이 최상위 디렉토리입니다. 마지막 명령문이 실행되었는지 여부를 인쇄합니다. >좋은 결과를 얻으려면 약간의 깔끔함이 필요합니다.

하지만 증분은 어떻습니까?

그런 다음 조건 문제가 있습니다 $PS2. 이전에 이 작업을 수행하는 방법을 보여 드렸지만 아래에서 계속 찾을 수 있습니다. 이는 근본적으로 범위 문제입니다. 하지만 그것보다 더 많은 일이 있습니다. 많은 printf \backspace 작업을 시작한 다음 문자 수의 균형을 맞추려고 하지 않는 한... 이런. 그래서 저는 이렇게 합니다:

PS1='$(ps1)${PS2c##*[$((PS2c=0))-9]}'

${parameter##expansion}하루를 다시 저장했습니다. 그런데 여기에 이상한 점이 있습니다. 실제로 변수 자체를 삭제하면서 변수를 설정하고 있다는 것입니다. 우리는 새로운 값(mid-strip 설정)을 우리가 제거하는 glob으로 사용합니다. 바라보다? ##*증분 변수의 머리부터 마지막 ​​문자( 의 모든 문자일 수 있음)까지 모든 문자를 제거 합니다 [$((PS2c=0))-9]. 이렇게 하면 값이 출력되지 않지만 여전히 할당됩니다. 멋지네요. 저는 그런 일을 한 번도 해본 적이 없습니다. 그러나 POSIX는 이것이 가장 이식성이 뛰어난 방법임을 보장합니다.

${parameter} $((expansion))이는 평가 위치에 관계없이 별도의 하위 셸에 설정하도록 요구하지 않고 현재 셸에 이러한 정의를 유지하는 POSIX 조항 덕분입니다 . 이것이 dash바로 안팎에서와 마찬가지로 안팎에서도 잘 작동하는 이유입니다 sh. 우리는 쉘/터미널 관련 이스케이프를 사용하지 않으며 변수가 스스로 테스트하도록 합니다. 이것이 바로 이식 가능한 코드의 이유입니다.bashzsh빠른.

나머지는 매우 간단합니다. 다시 재설정될 $PS2때까지 각 평가에서 카운터를 늘리면 됩니다. $PS1이와 같이:

PS2='$((PS2c=PS2c+1)) > '

이제 다음을 수행할 수 있습니다.

대시 데모

ENV=/tmp/prompt dash -i

/h/mikeserv > cd /etc
/etc > cd /usr/share/man/man3
/u/s/m/man3 > cat <<HERE
1 >     line 1
2 >     line 2
3 >     line $((PS2c-1))
4 > HERE
    line 1
    line 2
    line 3
/u/s/m/man3 > printf '\t%s\n' "$PS1" "$PS2" "$PS2c"
    $(ps1)${PS2c##*[$((PS2c=0))-9]}
    $((PS2c=PS2c+1)) >
    0
/u/s/m/man3 > cd ~
/h/mikeserv >

상하이 데모

bash또는 에서도 동일하게 작동합니다 sh.

ENV=/tmp/prompt sh -i

/h/mikeserv > cat <<HEREDOC
1 >     $( echo $PS2c )
2 >     $( echo $PS1 )
3 >     $( echo $PS2 )
4 > HEREDOC
    4
    $(ps1)${PS2c##*[$((PS2c=0))-9]}
    $((PS2c=PS2c+1)) >
/h/mikeserv > echo $PS2c ; cd /
0
/ > cd /usr/share
/u/share > cd ~
/h/mikeserv > exit

위에서 말했듯이 가장 중요한 문제는 어디에서 계산을 수행할지 고려해야 한다는 것입니다. 상위 셸에서는 상태를 가져올 수 없으므로 거기서 계산을 수행하지 않습니다. 서브셸에서 상태를 확인할 수 있습니다. 여기서 계산을 수행합니다. 그러나 상위 쉘에서 정의합니다.

ENV=/dev/fd/3 sh -i  3<<\PROMPT
    ps1() { printf '$((PS2c=0)) > ' ; }
    ps2() { printf '$((PS2c=PS2c+1)) > ' ; }
    PS1=$(ps1)
    PS2=$(ps2)
PROMPT

0 > cat <<MULTI_LINE
1 > $(echo this will be line 1)
2 > $(echo and this line 2)
3 > $(echo here is line 3)
4 > MULTI_LINE
this will be line 1
and this line 2
here is line 3
0 >

답변2

이 접근 방식(하위 쉘에서 실행되는 함수)을 사용하면 메인 쉘 프로세스를 비틀지 않고 상태를 업데이트할 수 없습니다. 대신 기본 프로세스에서 실행되도록 기능을 예약하세요.

의 가치PROMPT_COMMAND변수는 프롬프트가 인쇄되기 전에 실행되는 명령으로 해석됩니다 PS1.

의 경우 PS2비교할 것이 없습니다. 그러나 트릭을 사용할 수 있습니다. 원하는 것은 산술 연산뿐이므로 하위 쉘을 포함하지 않는 산술 확장을 사용할 수 있습니다.

PROMPT_COMMAND='PS_COUNT=0'
PS2='$((++PS_COUNT))  '

산술 계산 결과가 마침내 프롬프트에 나타납니다. 숨기려면 존재하지 않는 배열 첨자로 전달하면 됩니다.

PS1='${nonexistent_array[$((PS_COUNT=0))]}\$ '

답변3

이는 I/O 집약적이지만 개수 값을 저장하려면 임시 파일을 사용해야 합니다.

ps_count_inc () {
   read ps_count < ~/.prompt_num
   echo $((++ps_count)) | tee ~/.prompt_num
}

ps_count_reset () {
   echo 0 > ~/.prompt_num
}

각 셸 세션마다 별도의 파일이 필요한지 걱정된다면(사소한 문제인 것 같습니다. 동시에 두 개의 서로 다른 셸에 여러 줄의 명령을 입력하시겠습니까?) mktemp각 셸에 대해 새 파일을 만들어야 합니다. 사용.

ps_count_reset () {
    rm -f "$prompt_count"
    prompt_count=$(mktemp)
    echo 0 > "$prompt_count"
}

ps_count_inc () {
    read ps_count < "$prompt_count"
    echo $((++ps_count)) | tee "$prompt_count"
}

답변4

할 수 없다이런 식으로 쉘 변수를 사용하면 그 이유를 이미 알 수 있습니다. 서브쉘은 프로세스가 환경을 상속하는 것과 똑같은 방식으로 변수를 상속합니다. 변경 사항이 적용됩니다.오직해당 프로세스와 해당 하위 프로세스에는 해당되지만 상위 프로세스에는 해당되지 않습니다.

다른 답변에 따르면 가장 간단한 방법은 데이터를 파일에 저장하는 것입니다.

echo $count > file
count=$(<file)

등.

관련 정보