How can I do

{$several_commands} | less

and have less considere it as several files and enable navigation using :n and :p.

That may not be the clearer explanation, so let us consider an example. I currently have a function

svndiff () 
    for a in `svn status | \grep ^M | sed 's/M       //'`;
        svn diff $a | less;

The purpose obviously is to see with less the difference of all my modified files. But with this syntax, I have to use key Q to close one "file" and open the next one. I would like to be able to navigate between files with the less commands :n (next file) and :p (previous file). How can I do that ?

  • 2
    You can’t, since the pipe doesn’t save the output. Would you be ok with temporary files? – Jeff Schaller Feb 13 '18 at 16:37
  • @JeffSchaller I am ok with temporary files, I will most probably accept the answer from ikkachu. – Tsathoggua Feb 14 '18 at 8:19

You could use process substitution:

less -f <(svn diff this) <(svn diff that)

But that's hard to use in a loop. Probably best to just use temporary files:

dir=$(mktemp -d)
set -f 
for file in $(svn status | \grep ^M | sed 's/M       //') ; do
    outfile=${file#.}             # remove leading dot (if any)
    outfile=${outfile//\//__}     # replace slashes (if any) with __
    svn diff "$file" > "$dir/$outfile";
    outfiles+=("$dir/$outfile")   # collect the filenames to an array
less "${outfiles[@]}"
rm -r "$dir"

The above tries to keep the filenames visible in the names of the temp files, with some cleanup for slashes and leading dots. (In case you get paths like ./foo/bar. I can't remember how svn outputs the file names, but anyway...)

The array is there to keep the order, though as @Kusalananda said, we could just do "$dir"/* instead, if the order doesn't matter. set -f and IFS=$'\n' in case someone creates file names with glob characters or white space.

Of course we could simplify the script a bit and create, say numbered temp files instead.

  • Since all files are written to $dir you could possibly just less "$dir"/* and drop the use of $outfiles completely. – Kusalananda Feb 13 '18 at 16:58
  • @Kusalananda That would mean, though, that you possibly less the output out of order, which may or may not be desired. – Ned64 Feb 13 '18 at 22:26
  • @Ned64 Hmm... With the output filename set the way he does it here, it will depend on what "whatever" in the code does. – Kusalananda Feb 13 '18 at 22:30
  • It works if I do outfiles+=("$dir/$outfile"). I didn't knew about mktemp, interresting command. – Tsathoggua Feb 14 '18 at 7:59
  • @Tsathoggua, d'OH! Yes, of course the output directory should be there. Thanks for the note. – ilkkachu Feb 14 '18 at 9:00

If you want to move with :n and :p there is no other way but to run the commands, output the outputs to files, then less them:

svndiff () 
    d=$(mktemp -d /tmp/svndiffsXXXXX)
    for a in $(svn status | \grep ^M | sed 's/M       //');
        svn diff "$a" > $(mktemp $d/diffXXXXX) 2>&1;
    less "$d"/diff*
    rm -fr "$d"

(If you need them in order let me know and we can apply numbering.)

Otherwise, you could call a shell executing all of your commands, then pipe the concatenated output to less.

  • There's no need to override the system's $TMPDIR by forcing the the path and name of the temporary directory. – Kusalananda Feb 14 '18 at 9:04
  • 1
    @Kusalananda, unless they want to give the directory a meaningful name... On a quick test, the mktemp versions from GNU coreutils and e.g. from FreeBSD work differently with regard to -t, so something like mktemp -d "${TMPDIR:-/tmp}/svndiffs.XXXXX" would be needed to use TMPDIR and work on both. yngh. – ilkkachu Feb 14 '18 at 9:15
  • @Kusalananda The existence of /tmp and our right to use it is guaranteed by the FSH Standard so no need to make things overly complicated, especially as we clean up after ourselves. – Ned64 Feb 14 '18 at 9:38
  • @Ned64 Perfectly reasonable. I was just thinking on about systems similar to our local cluster where we definitely want people to use $TMPDIR for temporary storage. But that's my local situation and possibly not relevant here. – Kusalananda Feb 14 '18 at 9:45

Using GNU Parallel you could do something like:

files=$(svn status | \grep ^M | sed 's/M       //' |
  parallel --files svn diff {})
less $files
rm $files

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.