ซีเรียลไลซ์ตัวแปรเชลล์ใน bash หรือ zsh


12

มีวิธีใดในการทำให้เชลล์เชลล์เป็นอนุกรม? สมมติว่าฉันมีตัวแปร$VARและฉันต้องการที่จะสามารถบันทึกลงในไฟล์หรืออะไรก็ตามแล้วอ่านมันอีกครั้งในภายหลังเพื่อรับค่าเดียวกันกลับมา?

มีวิธีพกพาในการทำเช่นนี้? (ฉันไม่คิดอย่างนั้น)

มีวิธีทำใน bash หรือ zsh หรือไม่


2
ข้อควรระวัง: เวอร์ชันคำตอบของฉันที่คุณตอบรับเมื่อวันก่อนมีปัญหาร้ายแรงที่จะทำให้แตกในบางสถานการณ์ ฉันได้เขียนมันใหม่เพื่อรวมการแก้ไข (และเพิ่มคุณสมบัติ) และคุณควรอ่านใหม่ตั้งแต่เริ่มต้นและเปลี่ยนรหัสของคุณเพื่อใช้เวอร์ชันคงที่
คาเลบ

^ อีกตัวอย่าง ^ ของความเป็นพลเมืองที่ดีของ @ Caleb
mikeserv

คำตอบ:


14

คำเตือน:ด้วยวิธีการใด ๆ เหล่านี้คุณต้องระวังว่าคุณกำลังไว้วางใจในความถูกต้องของไฟล์ข้อมูลเพื่อความปลอดภัยเนื่องจากพวกเขาจะถูกเรียกใช้งานเป็นรหัสเชลล์ในสคริปต์ของคุณ การรักษาความปลอดภัยเป็นสิ่งสำคัญยิ่งสำหรับความปลอดภัยของสคริปต์ของคุณ!

การใช้งานอินไลน์อย่างง่ายสำหรับการทำให้เป็นอนุกรมอย่างน้อยหนึ่งตัวแปร

ใช่ทั้ง bash และ zsh คุณสามารถทำให้เนื้อหาของตัวแปรเป็นแบบที่ง่ายต่อการดึงข้อมูลโดยใช้typesetbuiltin และ-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ฟังก์ชั่นbuiltin ในขณะที่ zsh จะดำเนินการนี้ด้วยtypesetแต่เนื่องจาก bash มีนามแฝงสำหรับสิ่งนี้ที่จะทำงานในแบบที่เราใช้typesetที่นี่เพื่อความเข้ากันได้ของ ksh

การใช้งานทั่วไปที่ซับซ้อนยิ่งขึ้น

การใช้งานด้านบนนั้นง่ายมาก แต่ถ้าคุณเรียกมันบ่อยครั้งคุณอาจต้องการให้ฟังก์ชั่นยูทิลิตี้เพื่อให้ง่ายขึ้น นอกจากนี้หากคุณเคยลองใส่ฟังก์ชั่นที่กำหนดเองข้างต้นคุณจะพบกับปัญหาเกี่ยวกับการกำหนดขอบเขตตัวแปร รุ่นนี้ควรกำจัดปัญหาเหล่านั้น

หมายเหตุสำหรับสิ่งเหล่านี้เพื่อรักษาความเข้ากันได้ข้าม 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นิพจน์ที่ขี้ขลาดนั้นจะตรงกับเหตุการณ์แรกของ 'เรียงพิมพ์' หรือ 'ประกาศ' เท่านั้นและเพิ่ม-gเป็นอาร์กิวเมนต์แรก มีความจำเป็นต้องจับคู่เหตุการณ์แรกเท่านั้นเนื่องจากStéphane Chazelasชี้ให้เห็นอย่างถูกต้องในความคิดเห็นมิฉะนั้นจะจับคู่กรณีที่สตริงที่ต่อเนื่องนั้นมีบรรทัดใหม่ตามตัวอักษรตามด้วยคำที่ประกาศหรือเรียงพิมพ์

นอกเหนือจากการแก้ไข pasing faux pasเบื้องต้นของฉันแล้วStéphaneยังแนะนำวิธีที่ง่ายกว่าในการแฮ็คสิ่งนี้ไม่เพียง แต่ทำตามขั้นตอนด้านปัญหาของการวิเคราะห์สตริง แต่อาจเป็นตะขอที่มีประโยชน์ในการเพิ่มฟังก์ชันการทำงานเพิ่มเติมโดยใช้ฟังก์ชัน wrapper ดำเนินการเมื่อมีการจัดหาข้อมูลกลับมาสิ่งนี้ถือว่าคุณไม่ได้เล่นเกมอื่น ๆ ที่มีคำสั่งประกาศหรือเรียงพิมพ์ แต่เทคนิคนี้จะง่ายต่อการใช้งานในสถานการณ์ที่คุณรวมฟังก์ชั่นนี้เป็นส่วนหนึ่งของฟังก์ชันอื่นของคุณเองหรือ คุณไม่ได้อยู่ในการควบคุมข้อมูลที่กำลังเขียนและมีการ-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เป็นbashเทียบเท่า'sksh , นอกจากนี้ยังสนับสนุนในเรื่องที่เป็นแบบพกพามากขึ้น คือ POSIX แต่ไม่รับอาร์กิวเมนต์ใด ๆ และเอาต์พุตนั้นขึ้นอยู่กับเชลล์ (แม้ว่าจะระบุไว้อย่างดีสำหรับเชลล์ POSIX ดังนั้นตัวอย่างเช่นเมื่อ bash หรือ ksh เรียกว่าเป็น) อย่าลืมอ้างอิงตัวแปรของคุณ การใช้ตัวดำเนินการแยก + glob ที่นี่ไม่สมเหตุสมผล typesetbashzshtypesettypesetexport -psh
Stéphane Chazelas

โปรดทราบว่า-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 ชื่อแทนจะไม่ทำงานภายในฟังก์ชั่น (จำเป็นต้องกำหนดในเวลาที่นิยามฟังก์ชั่น) และด้วยการทุบตีซึ่งหมายความว่าคุณจะต้องทำshopt -s expandaliasเมื่อไม่ได้โต้ตอบ ด้วยฟังก์ชั่นคุณสามารถเพิ่มdeclarewrapper เพื่อให้มันคืนค่าตัวแปรที่คุณระบุเท่านั้น
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 ให้สิ่งอำนวยความสะดวกที่ดีกว่าด้วยtypesetbuiltin 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

@Caleb วิธีการเกี่ยวกับการทำtypesetนามแฝงสำหรับtypeset -g?
Gilles 'หยุดความชั่วร้าย'

@Gilles ฉันคิดว่าหลังจาก Stephanie แนะนำวิธีการใช้งาน แต่ฉันไม่แน่ใจว่าจะตั้งค่านามแฝงที่จำเป็นในการขยายตัวเลือกข้ามเชลล์ได้อย่างไร บางทีคุณอาจใช้คำตอบของคุณเป็นทางเลือกแทนฟังก์ชั่นที่ผมรวมไว้
Caleb

0

ขอบคุณมากที่ @ stéphane-chazelas ซึ่งชี้ให้เห็นปัญหาทั้งหมดที่เกิดขึ้นกับความพยายามครั้งก่อนของฉันตอนนี้ดูเหมือนจะทำงานเพื่อจัดลำดับอาร์เรย์ให้ stdout หรือเป็นตัวแปร

เทคนิคนี้ไม่ได้แยกวิเคราะห์อินพุต (ต่างจากdeclare -a/ declare -p) และปลอดภัยต่อการแทรกตัวเมตาอักขระในข้อความที่ต่อเนื่อง

หมายเหตุ: การขึ้นบรรทัดใหม่จะไม่ถูก Escape เนื่องจากreadลบ\<newlines>คู่อักขระดังนั้น-d ...ต้องส่งผ่านไปยังการอ่านแทนจากนั้นการขึ้นบรรทัดใหม่ที่ไม่ใช้ค่า Escape จะถูกสงวนไว้

ทั้งหมดนี้ได้รับการจัดการในunserialiseฟังก์ชั่น

มีการใช้อักขระเวทมนตร์สองตัวตัวคั่นฟิลด์และตัวแยกเรคคอร์ด (เพื่อให้สามารถใช้หลายอาร์เรย์ในการสตรีมเดียวกันได้)

ตัวละครเหล่านี้สามารถกำหนดเป็นFSและRSแต่ไม่สามารถกำหนดเป็นตัวละครเพราะการขึ้นบรรทัดใหม่หนีจะถูกลบออกโดยnewlineread

อักขระ escape ต้องเป็น\แบ็กสแลชเนื่องจากเป็นสิ่งที่ใช้readเพื่อหลีกเลี่ยงอักขระที่ถูกจดจำเป็นIFSอักขระ

serialiseจะทำให้เป็นอันดับ"$@"เพื่อ stdout 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 กับ:

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เคารพตัวละคร escape \(เว้นแต่คุณจะผ่านแฟล็ก -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

ใช่ .... ฉันลืมเกี่ยวกับพื้นที่สีขาวยุบการดูแลเป็นพิเศษเมื่อใช้เป็นตัวคั่นฟิลด์ในการอ่าน ฉันดีใจที่คุณอยู่บนลูกบอลวันนี้! คุณถูกต้องเกี่ยวกับ -d "" เพื่อหลีกเลี่ยงการหลบหนี \ n แต่ในกรณีของฉันฉันต้องการอ่านกระแสข้อมูลอนุกรม - ฉันจะปรับคำตอบด้วย ขอบคุณ!
Sam Liddicott

readหนีขึ้นบรรทัดใหม่ไม่อนุญาตให้มีการเก็บรักษาไว้ก็จะทำให้มันหายไปเมื่อ backslash-newline สำหรับreadเป็นวิธีการต่อสายตรรกะไปยังสายทางกายภาพอื่น แก้ไข: อ่าฉันเห็นคุณพูดถึงปัญหากับการขึ้นบรรทัดใหม่แล้ว
Stéphane Chazelas


-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's bash- debug output จะปลอดภัยโดยอัตโนมัติสำหรับ re-input ไปยัง shell สิ่งนี้ทำงานได้โดยไม่คำนึงถึงจำนวนของ'hard-quote ใน"$VAR":

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

$VAR สามารถตั้งค่าในภายหลังเป็นค่าที่บันทึกในสคริปต์ใด ๆ ที่พา ธ ต่อไปนี้ใช้ได้กับ:

. ./VAR.file

ฉันไม่แน่ใจว่าคุณพยายามเขียนอะไรในคำสั่งแรก $$PID ของเชลล์ทำงานหรือไม่คุณได้รับการอ้างถึงผิดและหมายถึง\$อะไรหรือเปล่า? วิธีการพื้นฐานของการใช้เอกสารที่นี่สามารถทำงานได้ แต่ไม่ยุ่งยากวัสดุซับเดียว: สิ่งที่คุณเลือกเป็นเครื่องหมายสิ้นสุดคุณต้องเลือกสิ่งที่ไม่ปรากฏในสตริง
Gilles 'หยุดความชั่วร้าย'

คำสั่งที่สองไม่ทำงานเมื่อมี$VAR %คำสั่งที่สามไม่ได้ทำงานกับค่าที่มีหลายบรรทัด (แม้หลังจากเพิ่มเครื่องหมายคำพูดคู่หายไปอย่างเห็นได้ชัด)
Gilles 'หยุดความชั่วร้าย'

@Gilles - ฉันรู้ pid - ฉันใช้มันเป็นแหล่งที่ง่ายในการตั้งค่าตัวคั่นที่ไม่ซ้ำกัน คุณหมายความว่าอย่างไร "ไม่เสมอ" และฉันไม่เข้าใจว่าอัญประกาศคู่ใดหายไปทั้งหมดนี้คือการกำหนดตัวแปร เครื่องหมายคำพูดคู่จะทำให้สถานการณ์สับสนในบริบทนั้นเท่านั้น
mikeserv

@Gilles - ฉันถอนสิ่งที่ได้รับมอบหมาย - envที่อาร์กิวเมนต์ ฉันยังคงสงสัยว่าคุณหมายถึงอะไรในหลาย ๆ บรรทัด - sedลบทุกบรรทัดจนกว่าจะพบจนถึงบรรทัดVAR=สุดท้าย - เพื่อให้ทุกบรรทัด$VARผ่านไป คุณช่วยยกตัวอย่างที่ทำให้แตกได้ไหม?
mikeserv

อาขอโทษวิธีที่สามใช้งานได้ (ด้วยการแก้ไขข้อความ) ทึกทักเอาเองว่าชื่อตัวแปร (ที่นี่VAR) นั้นไม่ได้เปลี่ยนไปPWDหรืออย่าง_อื่นที่เชลล์บางตัวกำหนด วิธีที่สองต้องใช้ bash; รูปแบบเอาต์พุตจาก-vไม่ได้มาตรฐาน (ไม่มีเส้นประ, ksh93, mksh และ zsh ทำงาน)
Gilles 'ดังนั้นหยุดความชั่วร้าย'

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

เวลานี้ผ่านการทดสอบแล้ว


ฉันเห็นคุณไม่ได้ทดสอบ! ตรรกะหลักทำงานได้ แต่นั่นไม่ใช่เรื่องยาก สิ่งที่ยากคือการอ้างถึงสิ่งต่าง ๆ อย่างถูกต้องและคุณไม่ได้ทำสิ่งนั้น ลองตัวแปรที่มีค่าประกอบด้วยบรรทัดใหม่', *ฯลฯ
กิลส์ 'ทีเราหยุดเป็นคนชั่ว'

echo "$LVALUE=\"$RVALUE\""ควรจะรักษาบรรทัดใหม่ด้วยและผลลัพธ์ใน cfg_file ควรเป็นเช่น: MY_VAR1 = "Line1 \ nLine 2" ดังนั้นเมื่อ eval MY_VAR1 จะมีบรรทัดใหม่เช่นกัน แน่นอนคุณอาจมีปัญหาหากค่าที่เก็บไว้ของคุณมี"อักขระ แต่นั่นก็สามารถได้รับการดูแลเช่นกัน
vadimbog

1
แต่ทำไมต้องลงคะแนนบางอย่างที่ตอบถูกคำถามที่ถามที่นี่? ข้างต้นทำงานได้ดีมากสำหรับฉันและใช้ทุกที่ในสคริปต์ของฉัน
vadimbog
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.