11

I want to use cat with wildcards in bash in order to print multiple small files (every file is one sentence) to standard output. However, the separate file contents are not separated by a newline, which I'd like to for ease of reading.

How can I add a file delimiter of some sort to this command?

6 Answers 6

13

Define a shell function which outputs an end of line after every file and use it instead of cat:

endlcat() {
  for file in "$@"; do
    cat -- "$file"
    echo
  done
}

then you can use endlcat *.

The for loop loops over all provided arguments ($@) which are already escaped by the shell when you use wildcards like *. The -- is required to not choke on file names starting with a dash. Finally the echo outputs a newline.

3
  • It seems rather obvious to use iteration in hindsight! I thought there might have been a flag in cat, but this works too! Thank you.
    – lennyklb
    Oct 14, 2015 at 15:24
  • You don't need the in "$@" - it's implicit.
    – l0b0
    Oct 14, 2015 at 23:05
  • 4
    being explicit is good for readability.
    – cas
    Oct 15, 2015 at 0:12
7

Just use a loop instead of simple cat:

for file in /path/to/targetdir/*; do
    echo "-------- $file -------"
    cat "$file" 
done
1
  • Works like the accepted answer, but that one also had the next step to the commandline so I accepted the other one.
    – lennyklb
    Oct 14, 2015 at 15:25
4

If you want file name between files, using tail:

tail -n +1 ./*

(In case you want tac instead of cat, some tail implementation have -r option for equivalent tac functional)

4

This will work:

sed '' -- *

You don't need any shell loops or otherwise, and this will always follow the output of a file with a \newline - excepting perhaps the very last.

Otherwise if you want a blank line to be output between the output of each file, this could work:

bpaste(){
    eval "paste -'sd\n' -- $(x=0;for f do printf "\"\${$((x+=1))}\" - ";done)"
}   </dev/null

That will always follow the output of a file with a \newline and an additional blank line.

You can call it like:

bpaste *
13
  • @cuonglm - the op wants a newline between files. this ensures a newline between files.
    – mikeserv
    Oct 14, 2015 at 17:02
  • @cuonglm - you should try again. for n in 1 2 3; do printf "$n" >"$n"; done; sed '' -- [123]
    – mikeserv
    Oct 14, 2015 at 17:18
  • @cuonglm - any sed must always print a \newline before it prints anything else, the last line of the last file might not get a \newline appended though. i could swear ive answered this question before.
    – mikeserv
    Oct 14, 2015 at 17:26
  • @cuonglm - i dont understand what you mean. that little bit of shell i just gave you prints 1\n2\n3. it works. it always does. what does it print for you?
    – mikeserv
    Oct 14, 2015 at 17:33
  • 1
    @DennisWilliamson: Using \n in RHS isn't standard. You can use sed -e '$G'.
    – cuonglm
    Oct 15, 2015 at 1:36
2

Similar to @terdon's answer, if you happen to prefer seeing the filename in the future as well, you can use the head command:

$ head *file*
==> file_1 <==
file 1 content

==> file_2 <==
file 2 content

==> file_3 <==
file 3 content

head defaults to first 10 lines, so using it without command options for your case (one sentence per file) is perfectly fine. Otherwise, you need the -n X option.

2

Another way - use grep -h to simply search for the empty string in each file. This will match all lines, regardless of how many or whether newline-terminated or not. grep results are always newline-terminated. The -h option suppresses prefixing each line of output with the filename it came from:

$ printf 'a' > a
$ printf 'b\nB' > b
$ printf 'c\n' > c
$ ls
a  b  c
$ cat -- *
ab
Bc
$ grep -h '' *
a
b
B
c
$ 

Or you could use GNU paste in -serial mode with newline as the delimiter:

$ paste -s -d '\n' -- *
a
b
B
c
$ 

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .