Skip to content

jq --in-place semantics#3488

Open
jubr wants to merge 15 commits into
jqlang:masterfrom
jubr:cursor/jq-in-place-semantics-72a6
Open

jq --in-place semantics#3488
jubr wants to merge 15 commits into
jqlang:masterfrom
jubr:cursor/jq-in-place-semantics-72a6

Conversation

@jubr
Copy link
Copy Markdown

@jubr jubr commented Feb 27, 2026

Add --in-place editing mode to jq with sed -i semantics.

This PR implements the --in-place option, allowing jq to modify a single input file directly, similar to sed -i. It ensures atomic updates by writing to a temporary file and then renaming it, preserving original file permissions and handling errors gracefully.

Historical context / references

Original feature request: #105
Prior attempt: #2616
Original -i implementation in jq history by @nicowilliams (commit 01fc816, later removed in b82c231)
Follow-up unhappy-path discussion: #704

Documentation (from new manual.yml)

--in-place / -i

This adds direct-file in-place editing semantics similar to sed -i:

  • the input file is also the output target;
  • jq writes to a temporary file in the same directory, then renames into place;
  • exactly one input file argument is required.

Example usage patterns

1) Update a package version from a shell variable

 jq -i ".version = ${VER}" package.json

2) Compact JSON rewrite in place

jq -c --in-place '.count += 1' state.json

3) Normalize key order in place

jq -S --in-place '.' config.json

4) Slurp + transform + write back to same file

jq -s --in-place 'map(.enabled = true)' records.json

5) Raw-text output in place (intentional non-JSON file replacement)

jq -r --in-place '.items[]' items.json

Invalid combinations / unhappy paths (now explicitly handled)

Missing file argument

jq --in-place '.+1'
# exits non-zero (usage error)

stdin target is rejected

printf '1\n' | jq --in-place '.+1' -
# exits non-zero (usage error)

Multiple file arguments are rejected

jq --in-place '.+1' a.json b.json
# exits non-zero (usage error)

--null-input cannot be combined with --in-place

jq -n --in-place '.+1' a.json
# exits non-zero (usage error)

Failure safety guarantees

If jq fails, the original input file is preserved:

  • compile error (e.g. invalid filter) leaves original file unchanged;
  • runtime error (e.g. error("boom")) leaves original file unchanged;
  • temp-file creation failure (e.g. non-writable directory) leaves original file unchanged.

Temporary output is cleaned up on error.

cursoragent and others added 8 commits February 27, 2026 09:43
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
@jubr jubr marked this pull request as draft February 27, 2026 12:09
cursoragent and others added 5 commits February 27, 2026 12:10
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
@jubr jubr marked this pull request as ready for review February 27, 2026 13:33
Co-authored-by: Jurgen Braam <jubr@users.noreply.github.com>
@jubr
Copy link
Copy Markdown
Author

jubr commented Mar 3, 2026

Going over #2616 (comment) I noticed:

An initial implementation could just refuse to edit-in-place any files not owned by geteuid(), and we can defer writing code to attempt to copy file owner/group/mode/ACL until later. On Windows it would just always go ahead.

I did a quick check:

  • POSIX
    The code does not check ownership (st_uid vs geteuid()) before in-place edit.
    In src/main.c around the IN_PLACE block (~668+), it validates arg shape and sets temp-file mode with stat/fchmod (~694-704), but there is no geteuid() ownership refusal path.

  • Windows
    It effectively always goes ahead (no ownership gate), matching the “On Windows it would just always go ahead” part.

We also might be able to go a different route: perhaps a cleaner solution would be to at the end fully fwrite the tmpfile content into the original sourcefile and truncate, thereby preserving the original owner/mode/acl. If this turns out to be too slow for large files we could even introduce a size threshold above which we fall back to rename_file() semantics.

@nicowilliams, @drm, @emanuele6 what are your thoughts on this?

@jubr
Copy link
Copy Markdown
Author

jubr commented Apr 24, 2026

Hi @itchyny, @wader any comments from you perhaps?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants