สร้างไฟล์ชั่วคราวในเชลล์สคริปต์ได้อย่างไร


155

ในขณะที่เรียกใช้สคริปต์ฉันต้องการสร้างไฟล์ชั่วคราวใน/tmpไดเรกทอรี

หลังจากเรียกใช้งานสคริปต์นั้นสคริปต์นั้นจะถูกกำจัด

วิธีทำในเชลล์สคริปต์?

คำตอบ:


198
tmpfile=$(mktemp /tmp/abc-script.XXXXXX)
: ...
rm "$tmpfile"

คุณสามารถตรวจสอบให้แน่ใจว่าไฟล์ถูกลบเมื่อสคริปต์ออก (รวมถึงการฆ่าและล้มเหลว) โดยการเปิดไฟล์ descriptor ไปยังไฟล์และลบทิ้ง ไฟล์เก็บไว้พร้อมใช้งาน (สำหรับสคริปต์ไม่ใช่กระบวนการจริงๆ แต่/proc/$PID/fd/$FDเป็นวิธีแก้ไข) ตราบใดที่ตัวให้คำอธิบายไฟล์เปิดอยู่ เมื่อมันถูกปิด (เคอร์เนลทำโดยอัตโนมัติเมื่อกระบวนการจบการทำงาน) ระบบไฟล์จะลบไฟล์

tmpfile=$(mktemp /tmp/abc-script.XXXXXX)
exec 3>"$tmpfile"
rm "$tmpfile"
: ...
echo foo >&3

4
คำตอบที่ดี, การแก้ปัญหาหรูหราพร้อมอธิบายไฟล์ในกรณีที่เป็นความผิดพลาด 1
ความวุ่นวาย

2
/proc- ยกเว้นระบบที่ไม่มี
Dennis Williamson

4
สิ่งที่ไม่exec 3> "$tmpfile"ทำอะไร? มีประโยชน์หรือไม่ถ้า tmpfile เป็นสคริปต์แบบสแตนด์อโลนเท่านั้น
Alexej Magura

5
คุณอ่านจาก FD ที่สร้างได้อย่างไร
eckes

3
"คุณสามารถใช้ cat <3 หรือคล้ายกันได้" จริงๆแล้วอ่านจากไฟล์ชื่อ 3 @ dragon788 นอกจากนี้จะให้cat <&3 Bad file descriptorฉันขอขอบคุณถ้าคุณแก้ไขหรือลบมัน ข้อมูลที่ผิดไม่ได้ช่วยอะไรมาก
Daniel Farrell

65

ใช้mktempเพื่อสร้างไฟล์หรือไดเรกทอรีชั่วคราว:

temp_file=$(mktemp)

หรือเป็นนักขัตฤกษ์:

temp_dir=$(mktemp -d)

ในตอนท้ายของสคริปต์คุณต้องลบไฟล์ชั่วคราว / dir:

rm ${temp_file}
rm -R ${temp_dir}

mktemp สร้างไฟล์ใน/tmpไดเรกทอรีหรือในวันที่กำหนดให้กับ--tmpdirอาร์กิวเมนต์


20
คุณสามารถใช้งานได้trap "rm -f $temp_file" 0 2 3 15ทันทีหลังจากสร้างไฟล์เพื่อให้เมื่อสคริปต์ออกหรือหยุดทำงานกับctrl-Cไฟล์นั้นจะยังคงถูกลบออก
wurtel

1
@wurtel จะเกิดอะไรขึ้นถ้าEXITเป็นเพียงเบ็ดเดียวtrap?
Hauke ​​Laging

4
@HaukeLaging จากนั้นกับดักไม่ทำงานถ้าสคริปต์หยุดทำงานด้วย Ctrl + C kill -9 $somepidสิ่งที่ควรทราบก็คือกับดักไม่ช่วยถ้าคุณ สัญญาณการฆ่านั้นคือการติดตั้งโดยไม่มีอะไรเกิดขึ้น
dragon788

5
@ dragon788 คุณเคยลองไหม คุณควร. bash -c 'echo $$; trap "echo foo" 0; sleep 5'
Hauke ​​Laging

กับดักEXITก็เพียงพอแล้ว
Kusalananda

15

หากคุณอยู่ในระบบที่มีmktempคุณควรใช้มันเป็นคำตอบอื่น ๆ

ด้วย POSIX toolchest:

umask 0177
tmpfile=/tmp/"$0"."$$"."$(awk 'BEGIN {srand();printf "%d\n", rand() * 10^10}')"
trap 'rm -f -- "$tmpfile"' INT TERM HUP EXIT
: > "$tmpfile"

จะเกิดอะไรขึ้นหากEXITเป็นตะขอเดียวสำหรับtrap?
Hauke ​​Laging

@HaukeLaging: tmpfileยังคงถูกลบออกก่อนที่สคริปต์จะออก แต่ไม่ใช่เมื่อสคริปต์ได้รับสัญญาณอื่น
cuonglm

นี่ไม่ใช่สิ่งที่เกิดขึ้นที่นี่ (GNU bash, เวอร์ชั่น 4.2.53)
Hauke ​​Laging

@HaukeLaging: คุณหมายถึงThat's not what happensอะไร
cuonglm

3
mktempมีต้นกำเนิดใน HP / UX ที่มีไวยากรณ์แตกต่างกัน ทอดด์ซีมิลเลอร์สร้างคนละแบบสำหรับ OpenBSD ในช่วงกลางยุค 90 (คัดลอกโดย FreeBSD และ NetBSD) และต่อมาทำให้มันพร้อมใช้งานเป็นยูทิลิตี้แบบสแตนด์อโลน (www.mktemp.org) นั่นคือสิ่งที่ใช้โดยทั่วไปบน Linux จนกว่าmktempยูทิลิตี้ส่วนใหญ่ที่เข้ากันได้จะถูกเพิ่มเข้าไปใน coreutils ของ GNU ในปี 2007 เพียงแค่บอกว่าไม่มีใครสามารถพูดได้จริงๆmktempคือ GNU อรรถประโยชน์
Stéphane Chazelas

14

เชลล์บางตัวมีคุณสมบัติในตัว

zsh

zshของ=(...)รูปแบบของกระบวนการเปลี่ยนตัวใช้แฟ้มชั่วคราว ยกตัวอย่างเช่นการขยายตัวไปยังเส้นทางของไฟล์ชั่วคราวที่ประกอบด้วย=(echo test)test\n

$ {cat $file; ls -l /dev/fd/3; echo test2 >&3; cat $file} 3<> ${file::==(echo test)}
test
lrwx------ 1 stephane stephane 64 Jan 30 11:19 /dev/fd/3 -> /tmp/zshMLbER0
test2

ไฟล์นั้นจะถูกลบโดยอัตโนมัติเมื่อคำสั่งเสร็จสิ้น

bash / zsh บน Linux

Here-files หรือ here-strings ในbashและzshมีการใช้งานเป็นไฟล์ชั่วคราวที่ถูกลบ

ดังนั้นถ้าคุณ:

exec 3<<< test

file descriptor 3 เชื่อมต่อกับไฟล์ชั่วคราวที่ถูกลบที่มีtest\nอยู่

คุณสามารถรับเนื้อหาด้วย:

cat <&3

หากบน Linux คุณสามารถอ่านหรือเขียนไฟล์นั้นผ่าน /dev/fd/3

$ exec 3<<< test
$ cat <&3
test
$ echo foo > /dev/fd/3
$ cat /dev/fd/3
foo

(เชลล์อื่นบางอันใช้ไพพ์หรืออาจใช้/dev/nullถ้าเอกสารที่นี่ว่างเปล่า)

POSIX

ไม่มีmktempยูทิลิตี้ POSIX POSIX ระบุmkstemp(template)C APIและm4ยูทิลิตี้มาตรฐานจะเปิดเผย API นั้นด้วยmkstemp()ฟังก์ชั่น m4 ด้วยชื่อเดียวกัน

mkstemp()ให้ชื่อไฟล์ที่มีส่วนแบบสุ่มที่รับประกันว่าจะไม่มีอยู่ในเวลาที่มีการเรียกใช้ฟังก์ชัน มันสร้างไฟล์ที่มีสิทธิ์ 0600 ในวิธีที่ไม่มีการแข่งขัน

ดังนั้นคุณสามารถทำได้:

tmpfile=$(
  echo 'mkstemp(template)' |
    m4 -D template="${TMPDIR:-/tmp}/baseXXXXXX"
) || exit

โปรดทราบว่าคุณต้องจัดการกับการล้างข้อมูลเมื่อออก แต่ถ้าคุณต้องการเขียนและอ่านไฟล์ในจำนวนครั้งที่กำหนดคุณสามารถเปิดและลบไฟล์ได้หลังจากสร้างไฟล์สำหรับ here-doc / here- วิธีการสตริงข้างต้น:

tmpfile=$(
  echo 'mkstemp(template)' |
    m4 -D template="${TMPDIR:-/tmp}/baseXXXXXX"
) || exit

# open once for writing, twice for reading:
exec 3> "$tempfile" 4< "$tempfile" 5< "$tempfile"

rm -f -- "$tmpfile"

cmd >&3   # store something in the temp file
exec 3>&- # fd no longer needed

# read the content twice:
cat <&4
cat <&5

คุณสามารถเปิดไฟล์เพื่ออ่านหนึ่งครั้งและย้อนกลับไปมาในระหว่างการอ่านสองครั้งอย่างไรก็ตามไม่มียูทิลิตี POSIX ที่สามารถทำการย้อนกลับ ( lseek()) ดังนั้นคุณจึงไม่สามารถทำได้ใน POSIX script ( zsh( sysseekbuiltin) และksh93( <#((...))operator) ทำมันแม้ว่า)


1
Bash ยังมีการทดแทนกระบวนการโดยใช้<()
WinnieNicklaus

3
@ WinieNicklaus ใช่ แต่นั่นไม่ได้ใช้ไฟล์ชั่วคราวจึงไม่เกี่ยวข้องกับที่นี่ กระบวนการทดแทนได้รับการแนะนำโดย ksh, คัดลอกโดยการทุบตีและ zsh และ zsh ขยายมันด้วยรูปแบบที่ =(...)3:
Stéphane Chazelas

7

นี่เป็นคำตอบที่ได้รับการปรับปรุงเล็กน้อยในบรรทัดของ Hauke ​​Laging:

#!/bin/bash

tmpfile=$(mktemp)  # Create a temporal file in the default temporal folder of the system

# Lets do some magic for the tmpfile to be removed when this script ends, even if it crashes
exec {FD_W}>"$tmpfile"  # Create file descriptor for writing, using first number available
exec {FD_R}<"$tmpfile"  # Create file descriptor for reading, using first number available
rm "$tmpfile"  # Delete the file, but file descriptors keep available for this script

# Now it is possible to work with the temporal file
echo foo >&$FD_W
echo bar >&$FD_W  # Note that file descriptor always concatenates, not overwrites

cat <&$FD_R

2
ควรสังเกตว่าเนื้อหานั้นมีอยู่เพียงครั้งเดียว คือถ้าฉันทำ cat <& $ FD_R เป็นครั้งที่สองจะไม่มีการสร้างเอาต์พุต ดูunix.stackexchange.com/questions/166482/... มีวิธีใดบ้างที่จะลบไฟล์โดยอัตโนมัติหากโปรแกรมขัดข้อง แต่ทำให้สามารถเข้าถึงได้หลายครั้ง
smihael

0

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

#!/bin/bash
echo $(mktemp /tmp/$(date +"%Y-%m-%d_%T_XXXXXX"))

เพื่อให้ฉันสามารถใช้มันเหมือน

$ some_command --with --lots --of --stuff | tee $(tmp)

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

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