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가 이를 달성하기 위해 사용하는 내장 함수로 변환 typeset하지만 bash에는 별칭이 있으므로 typesetksh 호환성을 위해 여기에서 사용할 수 있습니다.

함수를 사용한 보다 복잡한 일반 구현

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

bash/zsh 상호 호환성을 유지하기 위해 두 경우 모두 수정하여 코드가 둘 중 하나 또는 둘 다에서 작동하도록 할 것입니다 typeset. declare이렇게 하면 하나의 쉘 또는 다른 쉘에만 이 작업을 수행하는 경우 제거할 수 있는 약간의 부피와 혼란이 추가됩니다.

이를 위해 함수를 사용하는 것(또는 다른 함수에 코드를 포함하는 것)의 주요 문제점은 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. 여기에는 내보내지 않은 쉘 변수는 포함되지 않습니다. 동일한 셸에서 다시 읽고 정확히 동일한 변수 값을 얻을 수 있도록 출력이 올바르게 인용됩니다. POSIX가 아닌 구문을 사용하는 ksh와 같은 다른 셸에서는 출력을 읽지 못할 수도 있습니다 $'…'.

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
}

Bash에서 ( declare대신 사용 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예를 들어 변수 중 하나를 가져오더라 $PWD도 쉘은 해당 변수를 즉시 재설정합니다. 올바른 값을 얻게 됩니다. 시험해 보고 PWD=any_value직접 확인해 보세요.

그리고 적어도 GNU의 경우 디버그 출력은 셸에 다시 들어갈 수 있도록 자동으로 안전하게 인용되기 때문에 bash이는 다음의 하드 따옴표 수에 관계없이 작동합니다.'"$VAR"

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

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

. ./VAR.file

관련 정보