4

I saw there were several good answers for bash and even zsh(i.e. Here). Although I wasn't able to find a good one for fish.

Is there a canonical or clean one to prepend a string or a couple of lines into an existing file (in place)? Similar to what cat "new text" >> test.txt do for append.

Paiusco
  • 305
  • 1
  • 14
  • What do you want to prepend *to* the file? – gonidelis Jul 26 '20 at 22:20
  • Could be a simple string or a couple of lines (i.e. `#!/bin/bash\n`). Will update the question – Paiusco Jul 26 '20 at 22:27
  • 1
    It seems to me that the canonical sh/bash/ksh implementations would be pretty trivial to adopt. Did you run into a specific problem while trying to do so? – Charles Duffy Jul 26 '20 at 22:31
  • 1
    (by the way, the caveat that prepending to a file is inefficient on all filesystems with traditional unixlike semantics -- and is therefore best avoided when you don't know that your files are short -- will apply to fish as much as anywhere else). – Charles Duffy Jul 26 '20 at 22:33
  • Also, if the intent is to modify the file in place you should say so. Otherwise the answer is trivial: `echo a line; cat a-file`. – Kurtis Rader Jul 26 '20 at 23:20
  • Or `echo "new line" | cat - /path/to/original_file.txt # > /path/to/new_file.txt` (remove the `#` to write out to file immediately instead of to `stdout`). – CJK Jul 26 '20 at 23:59
  • 1
    @CJK that doesn't work as I get an `cat: test.txt: input file is output file` as return. I do not want to create another file, only prepend the same. – Paiusco Jul 27 '20 at 01:27
  • @KurtisRader Improved the question :) Is it clearer now? – Paiusco Jul 27 '20 at 01:31
  • @CharlesDuffy If you look [here](https://stackoverflow.com/a/42822247/7827713) you'll see there's a good one for `zsh`. I was looking for a similar one for fish. If I try `sed` it does work. – Paiusco Jul 27 '20 at 01:35
  • Which one, **specifically**, do you consider good? The first one, which creates a different output file? Or the other two, which require moreutils. – Charles Duffy Jul 27 '20 at 01:48
  • @CharlesDuffy the one that is stated as `Here String (zsh only)`. The first one doesn't work on fish either. – Paiusco Jul 27 '20 at 01:52
  • That "Here String (zsh only)" one requires moreutils. I'll add an answer using it with fish, but note that you'll need to install moreutils before it'll work, same as was the case with the original zsh version. – Charles Duffy Jul 27 '20 at 02:43
  • 1
    @Paiusco, ...btw, note that `sponge` is just creating a new file and renaming it over the destination when done (which is also what `sed -i` does); obvs., you can do that yourself. – Charles Duffy Jul 27 '20 at 15:22

2 Answers2

3

As part of fish's intentional aim towards simplicity, it avoids syntactic sugar found in zsh. The equivalent to the zsh-only code <<< "to be prepended" < text.txt | sponge text.txt in fish is:

begin; echo "to be prepended"; cat test.txt; end | sponge test.txt

sponge is a tool from the moreutils package; the fish version requires it just as much as the zsh original did. However, you could replace it with a function easily enough; consider the below:

# note that this requires GNU chmod, though it works if you have it installed under a
# different name (f/e, installing "coreutils" on MacOS with nixpkgs, macports, etc),
# it tries to figure that out.
function copy_file_permissions -a srcfile destfile
  if command -v coreutils &>/dev/null  # works with Nixpkgs-installed coreutils on Mac
    coreutils --coreutils-prog=chmod --reference=$srcfile -- $destfile
  else if command -v gchmod &>/dev/null  # works w/ Homebrew coreutils on Mac
    gchmod --reference=$srcfile -- $destfile
  else
    # hope that just "chmod" is the GNU version, or --reference won't work
    chmod --reference=$srcfile -- $destfile
  end
end

function mysponge -a destname
  set tempfile (mktemp -t $destname.XXXXXX)
  if test -e $destname
    copy_file_permissions $destname $tempfile
  end
  cat >$tempfile
  mv -- $tempfile $destname
end

function prependString -a stringToPrepend outputName
  begin
    echo $stringToPrepend
    cat -- $outputName
  end | mysponge $outputName
end

prependString "First Line" out.txt
prependString "No I'm First" out.txt
Charles Duffy
  • 280,126
  • 43
  • 390
  • 441
  • Alternatively, instead of using `sponge` you could write to a temporary file and replace the original with it afterwards - `echo "to be prepended" | cat - file > temp; mv temp file` – faho Jul 27 '20 at 14:54
  • Yup. If you read the comments on the question, the OP considered a `sponge`-based zsh answer the ideal they wanted to duplicate, which is why I went this route. – Charles Duffy Jul 27 '20 at 15:21
  • @CharlesDuffy Maybe it's worth to also add a non-inline solution like faho commented as an alternative. (maybe a fish homemade function that would make it transparent too) – Paiusco Jul 27 '20 at 16:24
2

For the specific case that file size is small to medium (fit in memory), consider using the ed program, which will avoid the temporary files by loading all data into memory. For example, using the following script. This approach avoid the need to install extra packages (moreutils, etc.).

#! /usr/env fish
function prepend
  set t $argv[1]
  set f $argv[2]
  echo '0a\n$t\n.\nwq\n' | ed $f
end
dash-o
  • 13,723
  • 1
  • 10
  • 37