상태 저장 bash 기능


16

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를 파일 이름에 추가 할 수 있습니다. 그러나 많은 파일로 시스템을 어지럽히 지 않는 더 나은 솔루션이 있기를 바랍니다.


파일 숨김을 사용한 WRT 충돌은 참조하십시오 man 1 mktemp.
goldilocks

내 편집 내용이 표시되어야합니다. 좋아할 것 같습니다.
mikeserv

답변:


14

여기에 이미지 설명을 입력하십시오

귀하의 질문에 언급 한 것과 동일한 결과를 얻으려면 필요한 것은 다음과 같습니다.

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)) > '

참고 : 최근에 yash를 배웠 으므로 어제 만들었습니다. 어떤 이유로 든 %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하고 가장 신뢰할 수있는 방법은 $IFSsplit on /입니다. 이후에는 전혀 신경 쓰지 않아도됩니다. 여기에서 모든 분할 $@은 다음과 같은 다음 명령에서 쉘의 위치 매개 변수 배열에 의해 정의됩니다 .

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

그래서 이것 좀 까다로운하지만 중요한 것은 그 우린 분할이다 $PWD/상징. 또한 매개 변수 확장을 사용 $last하여 가장 왼쪽 /슬래시 와 가장 오른쪽 슬래시 사이에 값이 발생한 후 모든 것에 할당합니다 . 이런 식으로 난 그냥에있어 경우에 알고 /및이 하나가 /$last여전히 전체를 동일합니다 $PWD$1비어 있습니다. 이것은 중요하다. 또한에 할당하기 전에 $last꼬리 끝에서 벗겨 냅니다.$PWD$@

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

그래서 여기 -만큼 ${1+is set}우리 printf처음 %c각 우리의 쉘의 인수 haracter - 우리가 우리의 현재의 각 디렉토리로 설정 한 $PWD- 덜 최상위 디렉토리 -에 분할 /. 따라서 기본적으로 모든 디렉토리의 첫 번째 문자를 $PWD제외하고 맨 위의 문자 만 인쇄합니다 . 이 $1설정은 전혀 설정되지 않은 경우에만 발생하지만 루트 /또는에서 /와 같이 제거 되지 않은 경우에만 발생합니다 /etc.

printf "$last > "

$last방금 최상위 디렉토리에 할당 한 변수입니다. 이제 이것이 최상위 디렉토리입니다. 마지막 문장의 인쇄 여부를 인쇄합니다. 그리고 >좋은 측정을 위해서는 깔끔한 약간의 시간이 걸립니다 .

그러나 증가에 대해 무엇입니까?

그리고 $PS2조건부 문제가 있습니다. 아래에서 여전히 찾을 수있는 방법을 이전에 보여주었습니다. 이것은 근본적으로 범위 문제입니다. 그러나 당신이 많은 printf \backspaces 를 시작하고 그들의 캐릭터 수의 균형을 맞추고 싶지 않다면 조금 더 있습니다. 그래서 나는 이것을한다 :

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

다시, ${parameter##expansion}하루를 저장합니다. 여기서 조금 이상합니다. 실제로 변수를 제거하는 동안 변수를 설정합니다. 미드 스트립으로 설정 한 새로운 값을 스트립 글로 사용합니다. 알 겠어? 우리는 ##*에서 아무것도 할 수있는 마지막 문자에 대한 우리의 증가 변수의 머리에서 모두 제거 [$((PS2c=0))-9]. 우리는 이런 식으로 값을 출력하지 않도록 보장하지만 여전히 값을 할당합니다. 꽤 멋지다-나는 전에 그것을 한 적이 없다. 그러나 POSIX는 이것이 가장 수행하기 쉬운 방법임을 보증합니다.

POSIX 지정 덕분에 ${parameter} $((expansion))이러한 정의를 평가하는 위치에 관계없이 별도의 하위 쉘로 설정하지 않고도 현재 쉘에 이러한 정의를 유지할 수 있습니다. 그것이 작동 이유입니다 dashsh단지뿐만 아니라 그것은에서와 같이 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 >

SH 데모

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 >

1
@mikeserv 우리는 원을 돌고 있습니다. 나는이 모든 것을 알고있다. 그러나 이것을 내 정의에서 어떻게 사용 PS2합니까? 이것은 까다로운 부분입니다. 귀하의 솔루션이 여기에 적용될 수 있다고 생각하지 않습니다. 다르게 생각하면 방법을 알려주세요.
Konrad Rudolph

1
@mikeserv 아니요, 관련이 없습니다. 죄송합니다. 자세한 내용은 내 질문을 참조하십시오. PS1PS2(설정하여 그것을 시도 프롬프트 명령으로 출력 쉘에서 특별한 변수는 PS1새로운 쉘 창에서 다른 값으로), 그들은 따라서 사용자 코드에서 매우 다르게 사용된다. 여기에 자신의 사용에 좀 더 정보입니다 : linuxconfig.org/bash-prompt-basics은
콘라드 루돌프

1
@KonradRudolph 두 번 정의하지 못하게하는 방법은 무엇입니까? 내 원래의 일이 ... 나는 당신의 대답을 봐야합니다 ... 이것은 항상 이루어집니다.
mikeserv

1
@mikeserv echo 'this프롬프트에서 PS2입력 한 다음 닫는 작은 따옴표를 입력하기 전에 값을 업데이트하는 방법을 설명 하십시오.
chepner

1
자,이 답변은 이제 공식적으로 훌륭합니다. 어쨌든 별도의 줄에 전체 경로를 인쇄 한 이후로이를 채택하지는 않더라도 빵 부스러기를 좋아합니다. i.imgur.com/xmqrVxL.png
Konrad Rudolph

8

이 방법 (서브 쉘에서 실행되는 기능)을 사용하면 뒤틀림없이 마스터 셸 프로세스의 상태를 업데이트 할 수 없습니다. 대신, 마스터 프로세스에서 기능이 실행되도록 정렬하십시오.

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

에 대해서는 PS2비교할 것이 없습니다. 그러나 대신 트릭을 사용할 수 있습니다. 원하는 것은 산술 연산이기 때문에 서브 쉘이 포함되지 않은 산술 확장을 사용할 수 있습니다.

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

산술 계산 결과가 프롬프트에 표시됩니다. 숨기려면 존재하지 않는 배열 첨자로 전달할 수 있습니다.

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

4

약간 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"
}

+1 파일이 작고 자주 액세스하는 경우 파일이 캐시되므로 본질적으로 공유 메모리로 작동하기 때문에 I / O는 그다지 중요하지 않습니다.
금발 미녀

1

당신은 할 수없는 쉘 변수이 방법을 사용하고 당신은 이미 그 이유를 이해합니다. A는 상속 변수에게 프로세스가 환경을 상속 정확히 같은 방식으로 서브 쉘 : 변경 사항을 적용했다 단지 그것과 자식과되지 않은 조상 프로세스.

다른 답변에 따르면 가장 쉬운 방법은 해당 데이터를 파일에 보관하는 것입니다.

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

기타.


물론 이런 식으로 변수를 설정할 수 있습니다. 임시 파일이 필요하지 않습니다. 서브 쉘에서 변수를 설정하고 해당 값을 흡수하는 상위 쉘로 값을 인쇄합니다. 서브 쉘에서 그 값을 계산하는 데 필요한 모든 상태를 얻을 수 있습니다.
mikeserv

1
@mikeserv 그것은 똑같은 것이 아니기 때문에 OP가 그러한 해결책이 효과가 없다고 말한 이유입니다 (질문에서 더 명확해야하지만). 당신이 말하는 것은 IPC 를 통해 다른 프로세스에 값을 전달하여 그 값을 무엇이든 할당 할 수 있다는 것입니다. OP가 원하거나 필요로하는 것은 여러 프로세스가 공유하는 전역 변수의 값에 영향을 미쳤으며 환경을 통해이를 수행 할 수는 없습니다. IPC에는 그다지 유용하지 않습니다.
goldilocks

남자, 나는 여기에 필요한 것을 완전히 잘못 이해했거나 다른 사람들이 가지고 있습니다. 정말 간단 해 보입니다. 내 편집 내용이 보입니까? 무슨 일이야?
mikeserv

@ mikeserv 나는 당신이 오해하고 공정하다고 생각하지 않습니다, 당신이 가진 것은 IPC의 한 형태이며 작동 할 수 있습니다. 왜 Konrad가 그것을 좋아하지 않는지는 분명하지 않지만, 충분히 유연하지 않으면 파일 숨김은 매우 간단합니다 (예 : 충돌을 피하는 방법도 있습니다 mktemp).
goldilocks

2
@mikeserv 의도 된 함수는 PS2 쉘에 의해 이 확장 . 이때 상위 셸에서 변수 값을 업데이트 할 기회가 없습니다.
chepner

0

참고로, 다음은 쉘 파일마다 고유하고 가능한 한 빨리 삭제되는 임시 파일을 사용하는 솔루션입니다 (질문에서 암시되는 것처럼 혼란을 피하기 위해).

# Yes, I actually need this to work across my systems. :-/
_mktemp() {
    local tmpfile="${TMPDIR-/tmp}/psfile-$$.XXX"
    local bin="$(command -v mktemp || echo echo)"
    local file="$($bin "$tmpfile")"
    rm -f "$file"
    echo "$file"
}

PS_COUNT_FILE="$(_mktemp)"

ps_count_inc() {
    local PS_COUNT
    if [[ -f "$PS_COUNT_FILE" ]]; then
        let PS_COUNT=$(<"$PS_COUNT_FILE")+1
    else
        PS_COUNT=1
    fi

    echo $PS_COUNT | tee "$PS_COUNT_FILE"
}

ps_count_reset() {
    rm -f "$PS_COUNT_FILE"
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.