Bash 또는 zsh에서 쉘 변수 직렬화

Bash 또는 zsh에서 쉘 변수 직렬화

쉘 변수를 직렬화하는 방법이 있습니까? 변수가 있고 $VAR이를 파일 등으로 저장한 다음 나중에 다시 읽어서 동일한 값을 다시 얻을 수 있기를 원한다고 가정해 보겠습니다.

이 작업을 수행하는 휴대용 방법이 있습니까? (나는 그렇게 생각하지 않는다)

bash나 zsh에서 할 수 있는 방법이 있나요?

답변1

경고:이러한 솔루션을 사용하면 데이터 파일이 스크립트에서 셸 코드로 실행되므로 데이터 파일의 무결성을 안전하게 신뢰할 수 있다는 점을 알아야 합니다. 이를 보호하는 것이 스크립트 보안에 가장 중요합니다!

하나 이상의 변수를 직렬화하기 위한 간단한 인라인 구현

typeset예, bash와 zsh 모두에서 내장 및 인수를 사용하여 쉽게 검색할 수 있는 방식으로 변수의 내용을 직렬화할 수 있습니다 -p. 출력 형식은 간단히 source출력을 통해 물건을 다시 가져올 수 있는 형식입니다.

 # You have variable(s) $FOO and $BAR already with your stuff
 typeset -p FOO BAR > ./serialized_data.sh

나중에 스크립트나 다른 스크립트에서 모두 다음과 같이 내용을 다시 가져올 수 있습니다.

# Load up the serialized data back into the current shell
source serialized_data.sh

이는 서로 다른 쉘 간에 데이터를 전달하는 것을 포함하여 bash, zsh 및 ksh에서 작동합니다. Bash는 이것을 내장 함수로 변환하고 declarezsh는 이것을 구현 하지만 bash에는 ksh 호환성을 위해 여기에서 typeset사용하기 위해 어느 쪽이든 작동할 수 있는 별칭이 있습니다 .typeset

함수를 사용하여 더욱 복잡하고 일반화된 구현

위의 구현은 정말 간단하지만 자주 호출하는 경우 유틸리티 함수를 제공하여 더 쉽게 만들 수 있습니다. 또한 위의 내용을 사용자 정의 함수에 포함시키려고 하면 변수 범위 지정에 문제가 발생하게 됩니다. 이 버전에서는 이러한 문제가 제거됩니다.

이들 모두에 대해 참고하세요. bash/zsh 상호 호환성을 유지하기 위해 typesetdeclare코드가 둘 중 하나 또는 둘 모두에서 작동하도록 코드가 두 경우 모두 수정될 것입니다. 이는 하나의 쉘 또는 다른 쉘에 대해서만 이 작업을 수행하는 경우 제거할 수 있는 약간의 부피와 혼란을 추가합니다.

이를 위해 함수를 사용하는 것(또는 다른 함수에 코드를 포함하는 것)의 주요 문제점은 함수가 typeset함수 내부에서 스크립트로 다시 소스될 때 전역 변수가 아닌 로컬 변수를 생성하는 것이 기본값인 코드를 생성한다는 것입니다.

이 문제는 여러 가지 해킹 중 하나로 해결할 수 있습니다. 이 문제를 해결하려는 초기 시도는 직렬화 프로세스의 출력을 구문 분석하여 플래그를 sed추가하여 -g생성된 코드가 다시 소스될 때 전역 변수를 정의하도록 하는 것이었습니다.

serialize() {
    typeset -p "$1" | sed -E '0,/^(typeset|declare)/{s/ / -g /}' > "./serialized_$1.sh"
}
deserialize() {
    source "./serialized_$1.sh"
}

펑키한 sed표현은 'typeset' 또는 'declare' 중 첫 번째 항목만 일치시키고 -g첫 번째 인수로 추가하는 것입니다. 첫 번째 항목만 일치해야 하기 때문에스테판 차젤라스주석에서 올바르게 지적하지 않으면 직렬화된 문자열에 문자 그대로의 개행 문자와 그 뒤에 선언 또는 조판이라는 단어가 포함되어 있는 경우에도 일치합니다.

초기 구문 분석을 수정하는 것 외에도잘못, 스테판도제안됨문자열 구문 분석 문제를 회피할 뿐만 아니라 데이터를 다시 소싱할 때 수행되는 작업을 재정의하는 래퍼 함수를 ​​사용하여 추가 기능을 추가하는 유용한 후크가 될 수 있는 덜 취약한 해킹 방법입니다. 선언 또는 조판 명령을 사용하여 다른 게임을 플레이할 수 있지만 이 기능을 자신의 다른 기능의 일부로 포함하거나 기록되는 데이터 및 작성 여부를 제어할 수 없는 상황에서는 이 기술을 구현하기가 더 쉬울 것입니다. 플래그가 추가 되지 않았습니다 -g. 별칭을 사용하여 비슷한 작업을 수행할 수도 있습니다.질의 답변구현을 위해.

결과를 더욱 유용하게 만들기 위해 인수 배열의 각 단어가 변수 이름이라고 가정하여 함수에 전달된 여러 변수를 반복할 수 있습니다. 결과는 다음과 같습니다.

serialize() {
    for var in $@; do
        typeset -p "$var" > "./serialized_$var.sh"
    done
}

deserialize() {
    declare() { builtin declare -g "$@"; }
    typeset() { builtin typeset -g "$@"; }
    for var in $@; do
        source "./serialized_$var.sh"
    done
    unset -f declare typeset
}

어느 솔루션을 사용하든 사용법은 다음과 같습니다.

# Load some test data into variables
FOO=(an array or something)
BAR=$(uptime)

# Save it out to our serialized data files
serialize FOO BAR

# For testing purposes unset the variables to we know if it worked
unset FOO BAR

# Load  the data back in from out data files
deserialize FOO BAR

echo "FOO: $FOO\nBAR: $BAR"

답변2

리디렉션, 명령 대체 및 매개변수 확장을 사용합니다. 공백과 특수 문자를 보존하려면 큰따옴표가 필요합니다. 후행은 x명령 대체에서 제거될 후행 개행을 저장합니다.

#!/bin/bash
echo "$var"x > file
unset var
var="$(< file)"
var=${var%x}

답변3

모두 직렬화 — POSIX

모든 POSIX 셸에서 다음을 사용하여 모든 환경 변수를 직렬화할 수 있습니다.export -p. 내보내지 않은 쉘 변수는 포함되지 않습니다. 출력은 올바르게 인용되므로 동일한 셸에서 다시 읽고 정확히 동일한 변수 값을 얻을 수 있습니다. 다른 셸에서는 출력을 읽지 못할 수도 있습니다. 예를 들어 ksh는 POSIX가 아닌 $'…'구문을 사용합니다.

save_environment () {
  export -p >my_environment
}
restore_environment () {
  . ./my_environment
}

일부 또는 전체 직렬화 — ksh, bash, zsh

Ksh(pdksh/mksh 및 ATT ksh 모두), bash 및 zsh는 다음과 같은 더 나은 기능을 제공합니다.typeset내장. typeset -p정의된 모든 변수와 그 값을 인쇄합니다(zsh는 로 숨겨진 변수의 값을 생략합니다 typeset -H). 출력에는 적절한 선언이 포함되어 있어 다시 읽을 때 환경 변수를 내보냅니다(그러나 다시 읽을 때 변수를 이미 내보낸 경우 내보내기가 취소되지 않음). 따라서 배열을 배열로 다시 읽을 수 있습니다. 여기서도 출력 올바르게 인용되었지만 동일한 쉘에서만 읽을 수 있음이 보장됩니다. 명령줄에서 직렬화할 변수 세트를 전달할 수 있습니다. 변수를 전달하지 않으면 모두 직렬화됩니다.

save_some_variables () {
  typeset -p VAR OTHER_VAR >some_vars
}

typesetBash 및 zsh에서는 함수 내부 명령문의 범위가 해당 함수로 지정되므로 함수에서 복원을 수행할 수 없습니다 . . ./some_vars변수의 값을 사용하려는 컨텍스트에서 실행해야 하며 내보낼 때 전역 변수가 전역으로 다시 선언되도록 주의해야 합니다. 함수 내의 값을 다시 읽고 내보내려면 임시 별칭이나 함수를 선언하면 됩니다. zsh에서:

restore_and_make_all_global () {
  alias typeset='typeset -g'
  . ./some_vars
  unalias typeset
}

declareBash에서( 대신 사용 typeset):

restore_and_make_all_global () {
  alias declare='declare -g'
  shopt -s expand_aliases
  . ./some_vars
  unalias declare
}

ksh에서는 typeset로 정의된 함수에서 지역 변수를 선언 function function_name { … }하고 로 정의된 함수에서 전역 변수를 선언합니다 function_name () { … }.

일부 직렬화 — POSIX

더 많은 제어를 원할 경우 변수 내용을 수동으로 내보낼 수 있습니다. 변수의 내용을 파일에 정확하게 인쇄하려면 내장을 사용하십시오 printf( 일부 쉘에서는 echo몇 가지 특수한 경우가 있고 echo -n개행을 추가합니다).

printf %s "$VAR" >VAR.content

$(cat VAR.content)명령 대체가 후행 개행 문자를 제거한다는 점을 제외하면 를 사용하여 이를 다시 읽을 수 있습니다 . 이러한 주름을 방지하려면 출력이 개행 문자로 끝나지 않도록 준비하십시오.

VAR=$(cat VAR.content && echo a)
if [ $? -ne 0 ]; then echo 1>&2 "Error reading back VAR"; exit 2; fi
VAR=${VAR%?}

여러 변수를 인쇄하려면 작은따옴표로 묶고 포함된 모든 작은따옴표를 로 바꿀 수 있습니다 '\''. 이러한 형식의 인용은 Bourne/POSIX 스타일 쉘에서 다시 읽을 수 있습니다. 다음 스니펫은 모든 POSIX 셸에서 작동합니다. 문자열 변수(및 해당 변수가 있는 쉘의 숫자 변수는 문자열로 다시 읽혀지지만)에 대해서만 작동하며 해당 변수가 있는 쉘의 배열 변수를 처리하려고 시도하지 않습니다.

serialize_variables () {
  for __serialize_variables_x do
    eval "printf $__serialize_variables_x=\\'%s\\'\\\\n \"\$${__serialize_variables_x}\"" |
    sed -e "s/'/'\\\\''/g" -e '1 s/=.../=/' -e '$ s/...$//'
  done
}

하위 프로세스를 분기하지 않지만 문자열 조작에 더 무거운 또 다른 접근 방식은 다음과 같습니다.

serialize_variables () {
  for __serialize_variables_var do
    eval "__serialize_variables_tail=\${$__serialize_variables_var}"
    while __serialize_variables_quoted="$__serialize_variables_quoted${__serialize_variables_tail%%\'*}"
          [ "${__serialize_variables_tail%%\'*}" != "$__serialize_variables_tail" ]; do
      __serialize_variables_tail="${__serialize_variables_tail#*\'}"
      __serialize_variables_quoted="${__serialize_variables_quoted}'\\''"
    done
    printf "$__serialize_variables_var='%s'\n" "$__serialize_variables_quoted"
  done
}

읽기 전용 변수를 허용하는 셸에서 읽기 전용 변수를 다시 읽으려고 하면 오류가 발생합니다.

답변4

printf 'VAR=$(cat <<\'$$VAR$$'\n%s\n'$$VAR$$'\n)' "$VAR" >./VAR.file

또 다른 방법은 '다음과 같이 모든 하드쿼트를 처리하는 것입니다.

sed '"s/'"'/&"&"&/g;H;1h;$!d;g;'"s/.*/VAR='&'/" <<$$VAR$$ >./VAR.file
$VAR
$$VAR$$

또는 다음을 사용하여 export:

env - "VAR=$VAR" sh -c 'export -p' >./VAR.file 

첫 번째와 두 번째 옵션은 변수 값에 문자열이 포함되어 있지 않다고 가정하여 모든 POSIX 셸에서 작동합니다.

"\n${CURRENT_SHELLS_PID}VAR${CURRENT_SHELLS_PID}\n" 

세 번째 옵션은 모든 POSIX 셸에서 작동하지만 _또는 같은 다른 변수를 정의하려고 시도할 수도 있습니다 PWD. 그러나 진실은 정의하려고 시도할 수 있는 유일한 변수는 쉘 자체에 의해 설정되고 유지된다는 것입니다. 따라서 예를 들어 export이들 중 하나에 대해 import 의 값을 수행하더라도 $PWD쉘은 단순히 해당 값을 다음과 같이 재설정합니다. 어쨌든 즉시 올바른 값을 얻으십시오. PWD=any_value직접 시도해보고 확인하십시오.

그리고 적어도 GNU의 경우 디버그 출력은 셸에 다시 입력할 수 있도록 자동으로 안전 따옴표로 지정되므로 다음의 하드 따옴표 bash수에 관계없이 작동합니다 .'"$VAR"

 PS4= VAR=$VAR sh -cx 'VAR=$VAR' 2>./VAR.file

$VAR나중에 다음 경로가 유효한 스크립트에서 저장된 값으로 설정할 수 있습니다.

. ./VAR.file

관련 정보