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


12

쉘 변수를 직렬화하는 방법이 있습니까? 내가 변수를 가지고 있고 $VAR파일이나 다른 것에 저장 한 다음 나중에 다시 읽어서 같은 값을 얻을 수 있기를 원합니까?

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

bash 또는 zsh에서 수행하는 방법이 있습니까?


2
주의 : 당신이 다른 날에 당신이 받아 들인 나의 대답 의 버전은 몇몇 시나리오에서 깨질 심각한 문제를 가지고있었습니다. 수정 사항을 포함하고 기능을 추가하기 위해 다시 작성했으며 고정 버전을 사용하려면 코드를 처음부터 다시 읽고 코드를 이식해야합니다.
Caleb

^ Caleb의 뛰어난 시민권의 또 다른 예.
mikeserv 2016 년

답변:


14

경고 : 이러한 솔루션 중 하나를 사용하면 스크립트에서 셸 코드로 실행되므로 데이터 파일의 무결성을 안전하게 유지해야합니다. 그것들의 보안은 스크립트 보안에 가장 중요합니다!

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

예, bash와 zsh에서 typeset내장 및 -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는 이것을 내장 declare함수로 변환 typeset하지만 zsh는 이것을 구현 하지만 bash에는 typesetksh 호환성을 위해 여기에서 사용할 수있는 별칭이 있습니다.

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

위의 구현은 실제로 간단하지만 자주 호출하면 유틸리티 기능을 제공하여 더 쉽게 만들 수 있습니다. 또한 위의 사용자 정의 함수에 위의 내용을 포함하려고하면 가변 범위 지정 문제가 발생합니다. 이 버전은 이러한 문제를 제거해야합니다.

/ 상호 호환성을 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번째 인수로 추가 해야합니다. Stéphane Chazelas 가 주석에서 올바르게 지적 했으므로 직렬화 된 문자열에 리터럴 줄 바꿈이 있고 선언 또는 조판이라는 단어가 포함 된 경우와도 일치 하기 때문에 첫 번째 항목 만 일치해야합니다 .

내 초기 파싱 가짜 pas 를 수정하는 것 외에도 Stéphane 은 문자열 파싱 문제를 회피 할뿐만 아니라 래퍼 함수를 ​​사용하여 작업을 재정 의하여 추가 기능을 추가하는 유용한 후크가 될 수있는 덜 취약한 방법을 제안 했습니다. 선언 또는 조판 명령으로 다른 게임을하고 있지 않다고 가정하지만,이 기능을 자신의 다른 기능의 일부로 포함하는 상황에서는이 기술을 더 쉽게 구현할 수 있습니다. 작성중인 데이터와 -g플래그 추가 여부를 제어 할 수 없었습니다 . 별명으로도 비슷한 작업을 수행 할 수 있습니다 . 구현에 대한 Gilles의 답변 을 참조하십시오 .

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

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"

declare는 IS bash당량 ksh의이 typeset. bash, zsh또한 지원 typeset하는 점에서, 그래서 typeset휴대 성이다. export -pPOSIX이지만 인수를 취하지 않으며 출력은 셸에 따라 다릅니다 (POSIX 셸에 대해 잘 지정되어 있기 때문에 bash 또는 ksh가로 호출되는 경우 sh). 변수를 인용하십시오; split + glob 연산자를 사용하는 것은 의미가 없습니다.
Stéphane Chazelas 2016 년

참고 -E일부 BSD의에서 발견된다 sed. 변수 값은 개행 문자를 포함 할 수 있으므로 sed 's/^.../.../'올바르게 작동하지 않을 수 있습니다 .
Stéphane Chazelas

이것이 바로 내가 찾던 것입니다! 쉘 사이에서 변수를 앞뒤로 밀어 넣는 편리한 방법을 원했습니다.
fwenom

의미 : 설치는로 a=$'foo\ndeclare bar' bash -c 'declare -p a'시작하는 줄을 출력합니다 declare. declare() { builtin declare -g "$@"; }전화하기 전에 source(그리고 나중에 설정을 해제 하기 전에)하는 것이 좋습니다
Stéphane Chazelas

2
@Gilles, 별칭은 함수 내부에서 작동하지 않으며 (함수 정의 시점에 정의해야 함) bash를 사용 shopt -s expandalias하면 대화식 이 아닌 경우 를 수행해야합니다 . 함수를 사용하면 declare래퍼를 향상시켜 지정한 변수 만 복원 할 수 있습니다.
Stéphane Chazelas

3

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

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

변수 이름도 파일에 저장하려고합니다.
user80551

2

모두 직렬화-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
}

bash 및 zsh에서는 함수 typeset내부의 명령문이 해당 함수의 범위에 있으므로 함수 에서 복원을 수행 할 수 없습니다 . . ./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
}

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


이 같은 변수에 제공 $PWD하고 $_- 아래에 자신의 의견을 참조하시기 바랍니다.
mikeserv 2016 년

@Caleb에 typeset대한 별칭을 만드는 방법은 typeset -g무엇입니까?
Gilles 'SO- 악한 중지'

@Gilles 스테파니가 함수 메소드를 제안한 후에는 생각했지만 쉘에서 필요한 별칭 확장 옵션을 이식 가능하게 설정하는 방법을 모르겠습니다. 어쩌면 내가 포함 한 기능에 대한 실용적인 대안으로 답을 넣을 수도 있습니다.
Caleb

0

많은 내 이전의 시도와 모든 문제를 지적 @ 스테판 - Chazelas가 덕분에이 이제 표준 출력 또는 변수로 배열에는 직렬화하기 위해 작동하는 것 같다.

이 기술은 declare -a/ 와 달리 입력을 셸 구문 분석하지 않으므로 declare -p직렬화 된 텍스트에서 메타 문자가 악의적으로 삽입되는 것을 방지합니다.

참고 : 문자 쌍을 read삭제 하기 때문에 줄 바꿈이 이스케이프되지 않으므로 대신 읽기 위해 전달 된 다음 이스케이프되지 않은 줄 바꿈이 유지됩니다.\<newlines>-d ...

이 모든 unserialise기능 에서 관리됩니다 .

필드 분리 자와 레코드 분리기의 두 가지 마법 문자가 사용되므로 여러 배열을 동일한 스트림으로 직렬화 할 수 있습니다.

이 문자는 다음과 같이 정의 할 수 없습니다 FS하고 RS있지만, 어느 쪽도 정의 할 수 있습니다 newline이스케이프 줄 바꿈에 의해 삭제 되었기 때문에 문자 read.

이스케이프 문자는 문자로 인식되지 않도록하기 위해 \사용되는 백 슬래시 여야합니다 .readIFS

serialise에는 직렬화됩니다 "$@"표준 출력에, serialise_to에 명명 된 varable에에는 직렬화합니다$1

serialise() {
  set -- "${@//\\/\\\\}" # \
  set -- "${@//${FS:-;}/\\${FS:-;}}" # ; - our field separator
  set -- "${@//${RS:-:}/\\${RS:-:}}" # ; - our record separator
  local IFS="${FS:-;}"
  printf ${SERIALIZE_TARGET:+-v"$SERIALIZE_TARGET"} "%s" "$*${RS:-:}"
}
serialise_to() {
  SERIALIZE_TARGET="$1" serialise "${@:2}"
}
unserialise() {
  local IFS="${FS:-;}"
  if test -n "$2"
  then read -d "${RS:-:}" -a "$1" <<<"${*:2}"
  else read -d "${RS:-:}" -a "$1"
  fi
}

다음과 같이 직렬화 해제하십시오.

unserialise data # read from stdin

또는

unserialise data "$serialised_data" # from args

예 :

$ serialise "Now is the time" "For all good men" "To drink \$drink" "At the \`party\`" $'Party\tParty\tParty'
Now is the time;For all good men;To drink $drink;At the `party`;Party   Party   Party:

(후행 줄 바꿈없이)

다시 읽어보십시오.

$ serialise_to s "Now is the time" "For all good men" "To drink \$drink" "At the \`party\`" $'Party\tParty\tParty'
$ unserialise array "$s"
$ echo "${array[@]/#/$'\n'}"

Now is the time 
For all good men 
To drink $drink 
At the `party` 
Party   Party   Party

또는

unserialise array # read from stdin

Bash read는 이스케이프 문자 \(-r 플래그를 전달하지 않은 경우)를 존중하여 입력 필드 구분 또는 행 구분과 같은 문자의 특수 의미를 제거합니다.

단순한 인수 목록 대신 배열을 직렬화하려면 배열을 인수 목록으로 전달하십시오.

serialise_array "${my_array[@]}"

랩핑 된 읽기이기 때문에 unserialise루프에서 사용할 수 read있지만 스트림은 줄 바꿈으로 구분되지 않습니다.

while unserialise array
do ...
done

요소에 인쇄 할 수없는 (현재 로케일) 또는 TAB 또는 줄 바꿈과 같은 제어 문자가 포함되어 bash있고로 zsh렌더링하면 작동하지 않습니다 $'\xxx'. bash -c $'printf "%q\n" "\t"'또는bash -c $'printf "%q\n" "\u0378"'
Stéphane Chazelas

젠장, 네 말이 맞아! printf % q를 사용하지 않고 공백을 피하기 위해 $ {@ // .. / ..} 반복을 사용하도록 대답을 수정하겠습니다.
Sam Liddicott

이 솔루션은 $IFS수정되지 않은 상태 에 따라 달라지며 이제 빈 배열 요소를 올바르게 복원하지 못합니다. 실제로, 다른 IFS 값을 사용하고 -d ''개행을 피할 필요가 없도록 하는 것이 더 합리적 입니다. 예를 들어 :필드 구분 기호로 사용 하고 해당 백 슬래시를 이스케이프 처리하고 IFS=: read -ad '' array가져 오기만 사용하십시오 .
Stéphane Chazelas

네 .... 필드 분리 문자로 읽을 때 공백 축소 특수 처리를 잊어 버렸습니다. 오늘 공을 들고 다행입니다. 당신은 \ n 이스케이프를 피하기 위해 -d ""에 대해 맞습니다. 그러나 제 경우에는 일련의 직렬화를 읽고 싶었습니다. 그러나 나는 대답을 조정할 것입니다. 감사!
Sam Liddicott

줄 바꿈을 피하면 보존 할 수 없으므로 한 번 사라집니다 read. backslash-newline for read는 논리적 행을 다른 실제 행으로 계속하는 방법입니다. 편집 : 아 이미 줄 바꿈 문제가 언급되어 있습니다.
Stéphane Chazelas

0

당신은 사용할 수 있습니다 base64:

$ VAR="1/ 
,x"
$ echo "$VAR" | base64 > f
$ VAR=$(cat f | base64 -d)
$ echo "${VAR}X"
1/ 
,xX

-2
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

첫 번째 명령에서 무엇을 쓰려고했는지 잘 모르겠습니다. $$실행중인 쉘의 PID \$입니까 , 따옴표가 잘못 되었거나 의미가 있습니까? here 문서를 사용하는 기본 접근 방식은 작동하도록 만들 수 있지만 하나의 라이너가 아닌 까다로운 작업입니다. 끝 마커로 선택하면 문자열에 나타나지 않는 것을 선택해야합니다.
Gilles 'SO- 악 그만해'

$VAR포함 하면 두 번째 명령이 작동하지 않습니다 %. 세 번째 명령은 여러 줄을 포함하는 값 (항상 누락 된 큰 따옴표를 추가 한 후에도)에서 항상 작동하지는 않습니다.
Gilles 'SO- 악마 그만해'

@Gilles-나는 그 pid를 알고있다-나는 그것을 독특한 구분 기호를 설정하는 간단한 소스로 사용했다. "항상 그렇지는 않다"는 것은 무엇을 의미합니까? 그리고 큰 따옴표가 누락 된 것을 이해하지 못합니다. 이들 모두 변수 할당입니다. 큰 따옴표는 해당 상황의 상황 만 혼동합니다.
mikeserv 2016 년

@Gilles-과제를 취소합니다 env. 나는 여전히 여러 줄에 대해 당신이 무엇을 의미하는지 궁금 합니다. 마지막 sed때까지 만날 VAR=때까지 모든 줄을 삭제 하십시오. 그래서 모든 줄 $VAR이 전달됩니다. 그것을 깨는 예를 제공해 줄 수 있습니까?
mikeserv 2016 년

아 죄송합니다, 세 번째 방법은 따옴표로 수정합니다. 글쎄, 변수 이름 (here VAR)이 변경되지 않았 PWD거나 _일부 쉘이 정의하는 다른 이름이라고 가정합니다 . 두 번째 방법은 bash가 필요합니다. 의 출력 형식 -v이 표준화되지 않았습니다 (대시, ksh93, mksh 및 zsh는 작동하지 않음).
Gilles 'SO- 악한 중지'

-2

거의 동일하지만 약간 다릅니다.

스크립트에서 :

#!/usr/bin/ksh 

save_var()
{

    (for ITEM in $*
    do
        LVALUE='${'${ITEM}'}'
        eval RVALUE="$LVALUE"
        echo "$ITEM=\"$RVALUE\""  
    done) >> $cfg_file
}

restore_vars()
{
    . $cfg_file
}

cfg_file=config_file
MY_VAR1="Test value 1"
MY_VAR2="Test 
value 2"

save_var MY_VAR1 MY_VAR2
MY_VAR1=""
MY_VAR2=""

restore_vars 

echo "$MY_VAR1"
echo "$MY_VAR2"

이번에는 위의 테스트를 거쳤습니다.


테스트하지 않은 것을 볼 수 있습니다! 핵심 논리가 작동하지만 어려운 것은 아닙니다. 어려운 일이 제대로 인용하고있는 것입니다. 값을 가진 줄 바꿈을 포함, 변수 시도 ', *
질 'SO-정지되는 악'

echo "$LVALUE=\"$RVALUE\""줄 바꿈도 유지해야하며 cfg_file의 결과는 다음과 같아야합니다. 저장된 값에 "char이 포함 된 경우 물론 문제가있을 수 있습니다 . 그러나 그것은 또한주의를 기울일 수 있습니다.
vadimbog 2016 년

1
Btw, 왜 여기에 묻는 질문에 올바르게 대답하는 것을 투표해야합니까? 위는 저에게 아주 잘 작동하며 스크립트의 모든 곳에서 사용합니까?
vadimbog 2016 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.