Jak utworzyć plik tymczasowy w skrypcie powłoki?

155

Podczas uruchamiania skryptu chcę utworzyć plik tymczasowy w /tmpkatalogu.

Po wykonaniu tego skryptu zostanie on wyczyszczony przez ten skrypt.

Jak to zrobić w skrypcie powłoki?

Bhuvanesh
źródło

Odpowiedzi:

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

Możesz upewnić się, że plik zostanie usunięty po wyjściu ze skryptu (włączając zabójstwa i awarie), otwierając deskryptor pliku i usuwając go. Plik pozostaje dostępny (dla skryptu; tak naprawdę nie dla innych procesów, ale /proc/$PID/fd/$FDjest obejściem), dopóki deskryptor pliku jest otwarty. Po zamknięciu (co jądro robi automatycznie po zakończeniu procesu) system plików usuwa plik.

tmpfile=$(mktemp /tmp/abc-script.XXXXXX)
exec 3>"$tmpfile"
rm "$tmpfile"
: ...
echo foo >&3
Hauke ​​Laging
źródło
4
Dobra odpowiedź, eleganckie rozwiązanie z deskryptorem pliku na wypadek awarii +1
chaos
2
/proc- z wyjątkiem systemów, które go nie mają.
Dennis Williamson
4
co robi exec 3> "$tmpfile"zrobić? Czy to nie jest użyteczne tylko wtedy, gdy plik tmp jest samodzielnym skryptem?
Alexej Magura
5
Jak czytasz z utworzonego FD?
eckes,
3
„Możesz użyć kota <3 lub czegoś podobnego”. właściwie to czyta z pliku o nazwie 3 @ dragon788. Również cat <&3da Bad file descriptor. Byłbym wdzięczny, jeśli to naprawisz lub usuniesz; dezinformacja niewiele pomaga.
Daniel Farrell
65

Użyj, mktempaby utworzyć tymczasowy plik lub katalog:

temp_file=$(mktemp)

Lub w kierunku direcotry:

temp_dir=$(mktemp -d)

Na końcu skryptu musisz usunąć plik tymczasowy / katalog:

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

mktemp tworzy plik w /tmpkatalogu lub w drectory podanym z --tmpdirargumentem.

chaos
źródło
20
Możesz użyć trap "rm -f $temp_file" 0 2 3 15zaraz po utworzeniu pliku, aby po wyjściu lub zatrzymaniu skryptu ctrl-Cplik był nadal usuwany.
wurtel
1
@wurtel Co się stanie, jeśli EXITjest to jedyny hak trap?
Hauke ​​Laging
4
@HaukeLaging Wtedy pułapka nie zostanie uruchomiona, jeśli skrypt zostanie zatrzymany za pomocą Ctrl + C. Należy zauważyć, że TRAP nie pomaga, jeśli ty kill -9 $somepid. Ten szczególny sygnał zabójstwa to natychmiastowa śmierć i nic innego się nie dzieje.
dragon788
5
@ dragon788 Czy próbowałeś tego? Powinieneś. bash -c 'echo $$; trap "echo foo" 0; sleep 5'
Hauke ​​Laging
EXITWystarczą pułapki .
Kusalananda
15

Jeśli korzystasz z systemu, który ma mktemp , powinieneś użyć go jako innych odpowiedzi.

Z zestawem narzędzi POSIX:

umask 0177
tmpfile=/tmp/"$0"."$$"."$(awk 'BEGIN {srand();printf "%d\n", rand() * 10^10}')"
trap 'rm -f -- "$tmpfile"' INT TERM HUP EXIT
: > "$tmpfile"
Cuonglm
źródło
Co się stanie, jeśli EXITjest to jedyny hak trap?
Hauke ​​Laging
@ HaukeLaging: tmpfilenadal będzie usuwany przed zakończeniem skryptu, ale nie, gdy skrypt otrzyma inne sygnały.
cuonglm
To nie dzieje się tutaj (GNU bash, wersja 4.2.53).
Hauke ​​Laging
@HaukeLaging: Co masz na myśli That's not what happens?
cuonglm
3
mktemppochodzi z HP / UX z inną składnią. Todd C. Miller stworzył inny dla OpenBSD w połowie lat 90. (skopiowany przez FreeBSD i NetBSD), a później udostępnił go również jako samodzielne narzędzie (www.mktemp.org). Jest to ten, który był zwykle używany w Linuksie, dopóki mktempw 2007 r. Nie dodano (głównie kompatybilnego) narzędzia do GNU coreutils. Nie można powiedzieć, że tak naprawdę nie można powiedzieć, że mktempto narzędzie GNU.
Stéphane Chazelas
14

Niektóre muszle mają wbudowaną funkcję.

zsh

zshjest =(...)podstawienia proces wykorzystuje plik tymczasowy. Na przykład =(echo test)rozwija się do ścieżki pliku tymczasowego, który zawiera 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

Plik ten jest automatycznie usuwany po zakończeniu wykonywania polecenia.

bash / zsh w systemie Linux.

Pliki-tutaj lub ciągi-tu są bashi zshsą implementowane jako usunięte pliki tymczasowe.

Więc jeśli to zrobisz:

exec 3<<< test

Deskryptor pliku 3 jest połączony z usuniętym plikiem tymczasowym, który zawiera test\n.

Możesz uzyskać jego zawartość za pomocą:

cat <&3

W systemie Linux można również czytać lub zapisywać do tego pliku za pośrednictwem /dev/fd/3

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

(niektóre inne powłoki używają potoków lub mogą użyć, /dev/nulljeśli tutaj dokument jest pusty).

POSIX

Nie ma mktempnarzędzia POSIX. POSIX określa jednak mkstemp(template)interfejs API języka C , a m4standardowe narzędzie udostępnia interfejs API z mkstemp()funkcją m4 o tej samej nazwie.

mkstemp()daje nazwę pliku z losową częścią, która nie istniała w momencie wywołania funkcji. Tworzy plik z uprawnieniami 0600 w sposób bez wyścigu.

Więc możesz zrobić:

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

Zauważ jednak, że musisz wyjść z czyszczenia po wyjściu, ale jeśli musisz tylko napisać i odczytać plik określoną liczbę razy, możesz go otworzyć i usunąć zaraz po utworzeniu, jak w przypadku tutaj-doc / here- powyższe podejście łańcuchowe:

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

Możesz otworzyć plik do odczytu raz i przewinąć do tyłu między dwoma odczytami, jednak nie ma narzędzia POSIX, które może zrobić to przewijanie ( lseek()), więc nie możesz tego zrobić przenośnie w skrypcie POSIX ( zsh( sysseekwbudowany) i ksh93( <#((...))operator) może zrób to jednak).

Stéphane Chazelas
źródło
1
Bash ma również podstawianie procesów przy użyciu<()
WinnieNicklaus
3
@WinnieNicklaus, tak, ale to nie korzysta z plików tymczasowych, więc tutaj nie ma znaczenia. Zmiana procesu został wprowadzony KSH kopiowane bash i zsh i zsh rozszerzył się 3 postać: =(...).
Stéphane Chazelas,
7

Oto nieco ulepszona odpowiedź w linii 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
Łabędzie
źródło
2
Należy zauważyć, że treść jest dostępna tylko raz. To znaczy, jeśli zrobię cat <& $ FD_R po raz drugi, nie zostanie wygenerowany żaden wynik. Zobacz unix.stackexchange.com/questions/166482/… . Czy istnieje jakiś sposób, aby plik został automatycznie usunięty, jeśli program ulegnie awarii, ale zostanie udostępniony wiele razy?
smihael
0

Mój przepływ pracy zwykle z plikami tymczasowymi wynika z testowanego skryptu bash. Chcę teeto poprawić, aby zobaczyć, że działa, i zapisać dane wyjściowe do następnej iteracji mojego procesu. Utworzyłem plik o nazwietmp

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

żebym mógł go używać jak

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

Powodem, dla którego podoba mi się data i godzina sformatowana przed losowymi wartościami, jest to, że pozwala mi łatwo znaleźć plik tmp, który właśnie utworzyłem, i nie muszę myśleć o tym, jak nazwać go następnym razem (i skupić się na otrzymaniu skryptu Dang pracować).

Frank Bryce
źródło