diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28f4840..1edbfa0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `randomNorm`
- `sqrt`
- `randomTri`
+ - `tempFileExt`
### Fixed
diff --git a/doc/functions.inc.html b/doc/functions.inc.html
index b820255..f90dbe5 100644
--- a/doc/functions.inc.html
+++ b/doc/functions.inc.html
@@ -85,8 +85,9 @@
File and Directory isFile | Test whether a path is a file. | (path -- bool) |
fileExists | Check whether a filesystem entry exists. Uses go os.Lstat for determination. This means that broken symlink will return true. | (str|path -- bool) |
hardLink | Create a hard link. | (path:source path:dest -- ) |
- tempFile | Create a temporary file and push its path. | (-- str) |
- tempDir | Return the OS-specific temporary directory via os.TempDir (for example, $TMPDIR on Unix or %TMP% on Windows). | (-- str) |
+ tempFile | Create a temporary file and push its path. Uses go os.CreateTemp with dir="" (defaults to os.TempDir) and pattern msh-, so the filename starts with msh- plus a random suffix. The file is not removed automatically; use rm/rmf when you are done. | (-- path) |
+ tempFileExt | Create a temporary file with a required extension and push its path. The extension input is canonicalized to start with . if needed. Uses go os.CreateTemp with dir="" (defaults to os.TempDir) and pattern msh-* plus the canonicalized extension, so the filename ends with the extension. The file is not removed automatically; use rm/rmf when you are done. | (str|path -- path) |
+ tempDir | Return the OS-specific temporary directory via os.TempDir (for example, $TMPDIR on Unix or %TMP% on Windows). | (-- path) |
rm | Remove a file; fails on IO errors. | (str -- ) |
rmf | Remove a file; ignore IO errors. | (str -- ) |
cp | Copy a file or directory. | (str:source str:dest -- ) |
diff --git a/doc/mshell.md b/doc/mshell.md
index a22f95c..6bfa6c1 100644
--- a/doc/mshell.md
+++ b/doc/mshell.md
@@ -227,8 +227,9 @@ Metadata values must be static: strings (single or double quoted), integers, flo
- `isFile`: Check if path is a file. `(path -- bool)`
- `fileExists`: Check whether a file or directory exists. `(str|path -- bool)`
- `hardLink`: Create a hard link. `(existingSourcePath newTargetPath -- )`
-- `tempFile`: Create a temporary file, and put the full path on the stack. `( -- str)`
-- `tempDir`: Return path to the OS specific temporary directory. No checks on permission or existence, so never fails. See [`os.TempDir`](https://pkg.go.dev/os#TempDir) in golang. `$TMPDIR` or `/tmp` for Unix, `%TMP%`, `%TEMP%, %USERPROFILE%`, or the Windows directory (`C:\Windows`). `( -- str)`
+- `tempFile`: Create a temporary file with go [`os.CreateTemp`](https://pkg.go.dev/os#CreateTemp) using `dir=""` (defaults to [`os.TempDir`](https://pkg.go.dev/os#TempDir)) and pattern `msh-`, so the filename starts with `msh-` plus a random suffix. Pushes the full path. The file is not removed automatically; use `rm`/`rmf` when you are done. `( -- path)`
+- `tempFileExt`: Create a temporary file with a required extension. The extension input is canonicalized to start with `.` if needed. Uses go [`os.CreateTemp`](https://pkg.go.dev/os#CreateTemp) with `dir=""` (defaults to [`os.TempDir`](https://pkg.go.dev/os#TempDir)) and pattern `msh-*` plus the canonicalized extension, so the filename ends with the extension. Pushes the full path. The file is not removed automatically; use `rm`/`rmf` when you are done. `(str|path -- path)`
+- `tempDir`: Return path to the OS specific temporary directory. No checks on permission or existence, so never fails. See [`os.TempDir`](https://pkg.go.dev/os#TempDir) in golang. `$TMPDIR` or `/tmp` for Unix, `%TMP%`, `%TEMP%, %USERPROFILE%`, or the Windows directory (`C:\Windows`). `( -- path)`
- `rm`: Remove file. Will stop execution on IO error, including file not found. `(str -- )`
- `rmf`: Remove file. Will not stop execution on IO error, including file not found. `(str -- )`
- `cp`: Copy file or directory. `(str:source str:dest -- )`
diff --git a/mshell/BuiltInList.go b/mshell/BuiltInList.go
index 18bf4f9..41afb6c 100644
--- a/mshell/BuiltInList.go
+++ b/mshell/BuiltInList.go
@@ -137,6 +137,7 @@ var BuiltInList = map[string]struct{}{
"take": {},
"tempDir": {},
"tempFile": {},
+ "tempFileExt": {},
"title": {},
"toDt": {},
"toFixed": {},
diff --git a/mshell/Evaluator.go b/mshell/Evaluator.go
index db411bd..b3d1a63 100644
--- a/mshell/Evaluator.go
+++ b/mshell/Evaluator.go
@@ -2080,6 +2080,33 @@ MainLoop:
if err != nil {
return state.FailWithMessage(fmt.Sprintf("%d:%d: Error creating temporary file: %s\n", t.Line, t.Column, err.Error()))
}
+ if err := tmpfile.Close(); err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Error closing temporary file: %s\n", t.Line, t.Column, err.Error()))
+ }
+ // Dump the full path to the stack
+ stack.Push(MShellPath{tmpfile.Name()})
+ } else if t.Lexeme == "tempFileExt" {
+ extValue, err := stack.Pop()
+ if err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Cannot do 'tempFileExt' operation on an empty stack.\n", t.Line, t.Column))
+ }
+
+ extStr, err := extValue.CastString()
+ if err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Cannot create tempFileExt with a %s.\n", t.Line, t.Column, extValue.TypeName()))
+ }
+
+ if extStr != "" && !strings.HasPrefix(extStr, ".") {
+ extStr = "." + extStr
+ }
+
+ tmpfile, err := os.CreateTemp("", "msh-*"+extStr)
+ if err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Error creating temporary file: %s\n", t.Line, t.Column, err.Error()))
+ }
+ if err := tmpfile.Close(); err != nil {
+ return state.FailWithMessage(fmt.Sprintf("%d:%d: Error closing temporary file: %s\n", t.Line, t.Column, err.Error()))
+ }
// Dump the full path to the stack
stack.Push(MShellPath{tmpfile.Name()})
} else if t.Lexeme == "tempDir" {