Concatenate multiple files but include filename as section headers
Solution 1
Was looking for the same thing, and found this to suggest:
tail -n +1 file1.txt file2.txt file3.txt
Output:
==> file1.txt <==
<contents of file1.txt>
==> file2.txt <==
<contents of file2.txt>
==> file3.txt <==
<contents of file3.txt>
If there is only a single file then the header will not be printed. If using GNU utils, you can use -v
to always print a header.
Solution 2
I used grep for something similar:
grep "" *.txt
It does not give you a 'header', but prefixes every line with the filename.
Solution 3
This should do the trick as well:
$ find . -type f -print -exec cat {} \;
./file1.txt
Content of file1.txt
./file2.txt
Content of file2.txt
Here is the explanation for the command-line arguments:
find = linux `find` command finds filenames, see `man find` for more info
. = in current directory
-type f = only files, not directories
-print = show found file
-exec = additionally execute another linux command
cat = linux `cat` command, see `man cat`, displays file contents
{} = placeholder for the currently found filename
\; = tell `find` command that it ends now here
You further can combine searches trough boolean operators like -and
or -or
. find -ls
is nice, too.
Solution 4
This should do the trick:
for filename in file1.txt file2.txt file3.txt; do
echo "$filename"
cat "$filename"
done > output.txt
or to do this for all text files recursively:
find . -type f -name '*.txt' -print | while read filename; do
echo "$filename"
cat "$filename"
done > output.txt
Solution 5
When there is more than one input file, the more
command concatenates them and also includes each filename as a header.
To concatenate to a file:
more *.txt > out.txt
To concatenate to the terminal:
more *.txt | cat
Example output:
::::::::::::::
file1.txt
::::::::::::::
This is
my first file.
::::::::::::::
file2.txt
::::::::::::::
And this is my
second file.
Nick
Updated on July 08, 2022Comments
-
Nick almost 2 years
I would like to concatenate a number of text files into one large file in terminal. I know I can do this using the cat command. However, I would like the filename of each file to precede the "data dump" for that file. Anyone know how to do this?
what I currently have:
file1.txt = bluemoongoodbeer file2.txt = awesomepossum file3.txt = hownowbrowncow cat file1.txt file2.txt file3.txt
desired output:
file1 bluemoongoodbeer file2 awesomepossum file3 hownowbrowncow
-
Nick about 13 yearsdidn't work. I just wrote some really ugly awk code: for i in $listoffiles do awk '{print FILENAME,$0,$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11}' $i >> concat.txt done
-
Chris Eberle about 13 years...care to elaborate? That's about as simple as bash code gets.
-
Chris Eberle about 13 years@Nick: your awk line shouldn't even work, considering that
$0
is the entire line, so you've actually got repeating columns in there... -
Chris Eberle about 13 years@Nick: Nifty solution otherwise :)
-
Nick about 13 years@Chris: yes, but its a lot uglier than I would like it to be. Maybe your code wasn't working for me because I'm using >> to catch the stdout?
-
Chris Eberle about 13 years@Nick: the other possibility is that your list of filenames have spaces.
for
in bash is rather dumb and splits on any whitespace. -
jayunit100 about 12 yearsmake sure not to use a * as the wildcard... it winds up infinitely recurring for some reason.
-
ArjunShankar about 12 yearsThis works with the GNU tail (part of GNU Coreutils) as well.
-
doubleDown over 11 yearsa cleaner syntax would be
head -n -0 file.txt file2.txt file3.txt
. Works forhead
in GNU coreutils -
Alec Jacobson over 10 yearsI want to concatenate a large number of files. If I use this (
tail +1 *.txt
) I get an errorToo many open files
. I fixed this with:OLD_ULIMIT=`ulimit -n`;ulimit -n 1024;tail +1 *.txt;ulimit -n $OLD_ULIMIT;
. Where 1024 was large enough for me. -
Asclepius over 10 yearsBe careful here.
for i in *
won't include subdirectories. -
AAlvz about 10 yearsCould you explain more what this command does? Is exactly what I Needed
-
AAlvz about 10 yearsand actually it works fine also without the
-print
instruction -
rshetye about 10 years@jayunit100: the infinite recurring is if your target file matches the wild card. name it such that it does not match.
-
Maxim_united about 10 yearsThis is linux' standard find command. It searches all files in the current directory, prints their name, then for each one, cats the file. Omitting the
-print
won't print the filename before thecat
. -
Maxim_united about 10 yearsYou can also use
-printf
to customize the output. For example:find *.conf -type f -printf '\n==> %p <==\n' -exec cat {} \;
to match the output oftail -n +1 *
-
antak almost 10 yearsOutput breaks if
*.txt
expands to only one file. In this regard, I'd advisegrep '' /dev/null *.txt
-
verboze about 9 years+1 for showing me a new use for grep. this met my needs perfectly. in my case, each file only contained one line, so it gave me a neatly formatted output that was easily parsable
-
Matt Fletcher almost 9 yearsI like this method as it gives much more flexibility with how you want to present the output!
-
dojuba almost 9 yearsIt is also quite straightforward to combine with
grep
. To use withbash
:find . -type f -print | grep PATTERN | xargs -n 1 -I {} -i bash -c 'echo ==== {} ====; cat {}; echo'
-
tripleee over 8 yearsThe
-f
is useful if you want to track a file which is being written to, but not really within the scope of what the OP asked. -
Jorge Bucaran over 8 years
grep
will only print file headers if there is more than one file. If you want to make sure to print the file path always, use-H
. If you don't want the headers use-h
. Also note it will print(standard input)
for STDIN. -
Frozen Flame over 8 yearsAwesome
-n +1
option! An alternative:head -n-0 file1 file2 file3
. -
Matt Fletcher over 8 years-printf doesn't work on mac, unless you want to
brew install findutils
and then usegfind
instead offind
. -
Fekete Sumér over 8 yearsExplanation:
for
loop goes through the list thels
command resulted.$ ls file{1..3}.txt
Result:file1.txt file2.txt file3.txt
each iteration willecho
the$file
string then it's piped into acut
command where I used.
as a field separator which breaks fileX.txt into two pieces and prints out the field1 (field2 is the txt) The rest should be clear -
anol almost 8 yearsYou can also use
ag
(the silver searcher): by default,ag . *.txt
prefixes each file with its name, and each line with its number. -
MediaVince over 7 yearsFor the sake of colors highlighting the filename:
find . -type f -name "*.txt" | xargs -I {} bash -c "echo $'\e[33;1m'{}$'\e[0m';cat {}"
-
Basic about 7 yearsIf you want to reference a different directory, you can control the name relative root with this one liner...
(cd /var/log; grep "" */*.log)
. This will print names relative to/var/log
-
DepressedDaniel about 7 yearsOf note, passing
-n
to grep also yields line numbers which allows you to write simple linters with pinpointing that could be picked up by, e.g., emacs. -
vdm over 6 yearsWorks great with both BSD tail and GNU tail on MacOS X. You can leave out the space between
-n
and+1
, as in-n+1
. -
kR105 almost 6 years
tail -n +1 *
was exactly was I was looking for, thanks! -
Patrick M almost 6 years
cat
from GNU coreutils 8.22 does not include the header by default, and does not appear to have an option for doing so. Combining yourfind
syntax with DS.'s answer, the commandfind . -type f -name file* -exec tail -n +1 {} +;
works exactly. -
Bar over 5 yearsThat, or
ls | xargs tail -n +1
should also work fine. -
JasonGenX about 5 yearsnice one. I wasn't aware of it.
-
banbh about 5 yearsIf you want colors you can use that fact that
find
allows multiple-exec
s:find -name '*.conf' -exec printf '\n\e[33;1m%s\e[0m\n' {} \; -exec cat {} \;
-
kolas about 5 yearsworks on MacOsX 10.14.4
sudo ulimit -n 1024; find -f . -name "*.rb" | xargs tail -n+1 > ./source_ruby.txt
-
kvantour over 4 yearsBut this does not answer the OP's question.
-
bwangel over 4 yearsThanks for your great answer. Can you explain the meaning of the
1
at the end of expression? -
kvantour over 4 yearsThanks. You can have a look at what is the meaning of 1 at the end of awk script
-
Coddy almost 4 yearsAlso if you need to concatenate without file names use
-q
(for silent) flag withhead
ortail
command. -
mblakesley almost 4 yearsI've always found it bizarre that this is the default behavior for
head
&tail
, yet it's not even an option forcat
. AND it still hasn't been added after all these years. Like, what?? I use this so often that I addedalias cats='head -n -0'
-
WinEunuuchs2Unix almost 4 years@Acumenus For myself I had to use:
String="${String//$'\n'::::::::::::::$'\n'/|}"
then:String="${String//::::::::::::::$'\n'/}"
and finally:String="${String//$'\n'/|}"
to make into a YAD array:IFS='|' read -ra OLD_ARR <<< "$String"
-
WinEunuuchs2Unix almost 4 years@Acumenus First I had to build the string field using:
String=$(sudo -u "$SUDO_USER" ssh "$SUDO_USER"@"$TRG_HOST" \ "find /tmp/$SUDO_USER/scp.*/*.Header -type f \ -printf '%Ts\t%p\n' | sort -nr | cut -f2 | \ xargs more | cat | cut -d'|' -f2,3" \ )
-
Asclepius almost 4 years@WinEunuuchs2Unix I don't follow. If you'd like, you can explain more clearly in a gist.github.com, and link to it instead.
-
WinEunuuchs2Unix almost 4 years@Acumenus Better yet I'll upload the script to github when done. It's just to copy root files between hosts using sudo because hosts don't have root accounts. The code in comments was to select headers for previous payloads. Kind of a fringe thing that won't interest most users.
-
mgutt over 3 yearsI tried
tail -n +1 *
as well, but it does not skip dirs and will be interrupted by them. Is there a way to skip dirs? -
stason over 3 yearsThis solution only prints the last few lines - not the whole files' contents.
-
ben-albrecht over 3 yearsThis does not work for me on MacOS. I only get the file contents.
-
young_souvlaki almost 3 yearsAs a bash function (SO comment doesn't support line breaks):
tails() { tail -v -n +1 "$@" | less }
. -
sourabh kesharwani about 2 yearsCool trick. I got the output as I needed. Thanks.