วิธีการส่งออกข้อความไปยังหน้าจอและไฟล์ภายในเชลล์สคริปต์?


49

ขณะนี้ฉันมีเชลล์สคริปต์ที่บันทึกข้อความไปยังไฟล์บันทึกเช่นนี้:

log_file="/some/dir/log_file.log"
echo "some text" >> $log_file
do_some_command
echo "more text" >> $log_file
do_other_command

เมื่อเรียกใช้งานสคริปต์นี้ไม่มีผลลัพธ์ไปที่หน้าจอและเนื่องจากฉันกำลังเชื่อมต่อกับเซิร์ฟเวอร์ผ่าน putty ฉันต้องเปิดการเชื่อมต่ออื่นและทำ "tail -f log_file_path.log" เนื่องจากฉันไม่สามารถยุติการทำงานได้ สคริปต์และฉันต้องการที่จะเห็นผลลัพธ์ในเวลาจริง

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

ทำอย่างไรจึงจะได้สิ่งนี้?

คำตอบ:


71

งานนี้:

command | tee -a "$log_file"

teeบันทึกอินพุตไปยังไฟล์ (ใช้-aเพื่อผนวกแทนที่จะเขียนทับ) และคัดลอกอินพุตไปยังเอาต์พุตมาตรฐานเช่นกัน


8

คุณสามารถใช้ here-doc และ จัดหามาสำหรับรุ่นตัวรวบรวมทั่วไปที่มีประสิทธิภาพและเป็นมิตร POSIX

. 8<<-\IOHERE /proc/self/fd/8

command
 
fn() { declaration ; } <<WHATEVER
# though a nested heredoc might be finicky
# about stdin depending on shell
WHATEVER
cat -u ./stdout | ./works.as >> expect.ed
IOHERE

เมื่อคุณเปิด heredoc คุณจะส่งสัญญาณเชลล์ด้วยโทเค็นอินพุต IOHERE ที่ควรเปลี่ยนทิศทางอินพุตไปยัง file-descriptor ที่คุณระบุจนกว่าจะพบที่ปลายอีกด้านของโทเค็นตัว จำกัด ของคุณ ฉันมองไปรอบ ๆ แต่ฉันไม่เห็นตัวอย่างของการใช้หมายเลข fd การเปลี่ยนเส้นทางตามที่ฉันได้แสดงไว้ด้านบนร่วมกับตัวดำเนินการ heredoc ถึงแม้ว่าการใช้จะถูกระบุไว้อย่างชัดเจนในแนวทางคำสั่งเชลล์คำสั่งพื้นฐาน POSIX คนส่วนใหญ่เพียงแค่ชี้ไปที่ stdin และ shoot แต่ฉันพบว่าการจัดหา scriptlets ด้วยวิธีนี้สามารถทำให้ stdin ฟรีและแอปที่เป็นส่วนประกอบจากการบ่นเกี่ยวกับเส้นทาง I / o ที่ถูกบล็อก

เนื้อหาของ heredoc จะถูกสตรีมไปยังไฟล์ descriptor ที่คุณระบุซึ่งจะถูกตีความว่าเป็นรหัสเชลล์และดำเนินการโดย builtin แต่ไม่ได้ระบุเส้นทางที่เฉพาะเจาะจงสำหรับ . หากพา ธ / proc / self ให้ปัญหากับคุณลอง / dev / fd / n หรือ / proc / $$ วิธีการเดียวกันนี้ทำงานบนท่อโดยวิธี:

cat ./*.sh | . /dev/stdin

อย่างน้อยก็อาจจะไม่ฉลาดเท่าที่ดู คุณสามารถทำเช่นเดียวกันกับ sh, แน่นอน แต่. วัตถุประสงค์คือเพื่อดำเนินการในสภาพแวดล้อมเชลล์ปัจจุบันซึ่งอาจเป็นสิ่งที่คุณต้องการและขึ้นอยู่กับเชลล์ของคุณมีแนวโน้มที่จะทำงานกับ heredoc มากกว่า ด้วยท่อที่ไม่ระบุชื่อมาตรฐาน

อย่างไรก็ตามอย่างที่คุณอาจสังเกตเห็นว่าฉันยังไม่ได้ตอบคำถามของคุณ แต่ถ้าคุณคิดเกี่ยวกับมันในลักษณะเดียวกับที่ heredoc สตรีมรหัสทั้งหมดของคุณเป็น. in ก็ให้คุณหนึ่งจุดง่าย ๆ ธรรมดา:

. 5<<EOIN /dev/fd/5 |\ 
    tee -a ./log.file | cat -u >$(tty)
script
 
more script
EOIN

ดังนั้นเทอร์มินัล stdout ทั้งหมดจากรหัสใด ๆ ที่ดำเนินการใน heredoc ของคุณจะถูกส่งออกมา เป็นเรื่องของหลักสูตรและสามารถทีจะออกจากท่อเดียวได้อย่างง่ายดาย ฉันรวมการเรียกแมวที่ไม่มีบัฟเฟอร์เพราะฉันไม่ชัดเจนเกี่ยวกับทิศทาง stdout ปัจจุบัน แต่อาจซ้ำซ้อน

คุณอาจตั้งคำถามเครื่องหมายคำพูดทับขวาที่หายไปในตัวอย่างที่สอง ส่วนนี้เป็นสิ่งสำคัญที่จะต้องเข้าใจก่อนที่คุณจะข้ามไปและอาจให้แนวคิดเล็กน้อยเกี่ยวกับวิธีการใช้งาน ตัว จำกัด heredoc ที่ยกมา (จนถึงตอนนี้เราใช้ IOHERE และ EOIN แล้วและเป็นครั้งแรกที่ฉันอ้างถึงแบ็กสแลชแม้ว่าเครื่องหมายอัญประกาศ "เดี่ยว" หรือ "สองเท่า" จะให้บริการเพื่อจุดประสงค์เดียวกัน) จะป้องกันเชลล์จากการขยายพารามิเตอร์ใด ๆ เนื้อหา แต่ตัว จำกัด ที่ไม่ได้กล่าวถึงจะเปิดเนื้อหาไว้เพื่อขยาย ผลที่ตามมาจากสิ่งนี้เมื่อคุณได้รับสิ่งนี้ มีที่มาอย่างมาก:

. 3<<HD ${fdpath}/3
: \${vars=$(printf '${var%s=$((%s*2))},' `seq 1 100`)} 
HD
echo $vars
> 4,8,12 
echo $var1 $var51
> 4 104

เนื่องจากฉันไม่ได้อ้างอิงตัว จำกัด heredoc เชลล์จึงขยายเนื้อหาตามที่อ่านในและก่อนที่จะแสดง descriptor ไฟล์ผลลัพธ์ เพื่อดำเนินการ สิ่งนี้ส่งผลให้คำสั่งถูกวิเคราะห์คำสองครั้ง - คำสั่งที่ขยายได้ เนื่องจาก I backslash อ้างถึงการขยายพารามิเตอร์ $ vars เชลล์จึงเพิกเฉยต่อการประกาศในรอบแรกและถอดเฉพาะแบ็กสแลชเพื่อให้เนื้อหาที่ถูกขยายการพิมพ์ printf ทั้งหมดสามารถถูกประเมินด้วย null เมื่อ สคริปต์ที่มาจากแหล่งที่สอง

ฟังก์ชั่นนี้โดยพื้นฐานแล้วสิ่งที่เปลือก eval ที่เป็นอันตรายสามารถจัดหาให้ได้แม้ว่าการอ้างจะง่ายต่อการจัดการใน heredoc มากกว่าการ eval และอาจเป็นอันตรายเท่า ๆ กัน หากคุณไม่ได้วางแผนอย่างรอบคอบคุณควรอ้างตัว จำกัด "EOF" ว่าเป็นเรื่องของนิสัย แค่พูด.

แก้ไข: เอ๊ะฉันกำลังมองกลับไปที่นี้และคิดว่ามันยืดเกินไปเล็กน้อย หากสิ่งที่คุณต้องทำคือเชื่อมเอาท์พุทหลาย ๆ ตัวให้เป็นไพพ์เดียวดังนั้นวิธีที่ง่ายที่สุดคือใช้:

{ or ( command ) list ; } | tee -a ea.sy >>pea.sy

curlies จะพยายามเรียกใช้เนื้อหาในเชลล์ปัจจุบันในขณะที่ parens จะย่อยออกโดยอัตโนมัติ ยังคงมีใครสามารถบอกคุณได้ว่าอย่างน้อยในความคิดของฉัน วิธีการแก้ปัญหา heredoc เป็นข้อมูลที่มีค่ามากกว่าโดยเฉพาะอย่างยิ่งถ้าคุณต้องการที่จะเข้าใจว่าเชลล์ทำงานอย่างไร

มีความสุข!


3

ฉันพบคำตอบนี้ในขณะที่ต้องการแก้ไขสคริปต์การติดตั้งเพื่อเขียนบันทึกการติดตั้ง

สคริปต์ของฉันเต็มไปด้วยคำสั่ง echo เช่น:

echo "Found OLD run script $oldrunscriptname"
echo "Please run OLD tmunsetup script first!"

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

#!/bin/bash
# A Shell subroutine to echo to screen and a log file

LOGFILE="junklog"

echolog()
(
echo $1
echo $1 >> $LOGFILE
)


echo "Going"
echolog "tojunk"

# eof

ตอนนี้ในสคริปต์ดั้งเดิมของฉันฉันสามารถเปลี่ยน 'echo' เป็น 'echolog' ซึ่งฉันต้องการผลลัพธ์ในล็อกไฟล์

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