In an effort to learn the shell better, and without always having to resort to xargs, I have been trying to discover any other ways to do:

find . -name *.tcl | xargs -I{} cat {}

xargs make it feel messy and I would like to know if there are multiple way to accomplish this.

EDIT: I did discover that another solution is to use:

find . -name "*.tcl" | cat `cat /dev/stdin`

I don't understand why I have to cat a filename before cat will see it as a file instead of a string though....

  • That's not really a question... Try rewording as a specific question. (Because, yes, there are many many ways to do that).
    – Brian
    Commented Oct 28, 2015 at 17:55
  • 1
    cat *.tcl perhaps? Or do you have a directory hierarchy of *.tcl files to process? Commented Oct 28, 2015 at 17:59
  • see unix.stackexchange.com/questions/9496/…
    – Jeff Schaller
    Commented Oct 28, 2015 at 19:50

5 Answers 5


You could also use find in a sub process and feed the output to cat :)

cat $(find . -name "*.tcl")
  • noting that if you have a file named "my file.tcl", the resulting cat command will be: "cat my file.tcl" and will fail...
    – Jeff Schaller
    Commented Oct 28, 2015 at 19:33
  • Ah yes! those evil files with spaces in their names :)
    – Rob
    Commented Oct 28, 2015 at 19:39
  • 1
    I really like that solution, I wish I knew of a way to mark more than one response as an answer, this is very helpful. Commented Oct 28, 2015 at 21:47
  • 1
    Note that it's not only spaces, it's all the characters present in $IFS (space tab and newline by default) and all the globbing characters. Do IFS=$'\n'; set -f; cat $(find . -name '*.tcl') to reduce the problem to newline only. Commented Oct 28, 2015 at 22:05
  • \n is not a problem, after the return from the sub process all those \n do not matter.. Think about it.. find returns one file per line, yet it gets interpreted by the cat command correctly ;).
    – Rob
    Commented Oct 29, 2015 at 14:15

You can use exec in find:

find . -name "*.tcl" -exec cat {} \;

Everything between the -exec and trailing \; is the command to run. Like xargs you replace the find results with {}. For each file that gets found cat is run (much like how xargs iterates over the list of files given via STDIN.

Some could argue this is more efficient since you're not using a pipe or starting another application.

  • 3
    How about using one call to cat- find . -name "*.tcl" -exec cat {} +
    – fd0
    Commented Oct 28, 2015 at 18:01
  • That's not the same thing as what the original question that was asked. Your method uses all the resulting filenames as an argument to cat. I don't know offhand the length of a command built in this manner but for a lot of files it might exceed that length. My method runs cat repeatedly, but once per file. For cat the results to the user appear to be the same, but there might be differences for other apps. Commented Oct 28, 2015 at 18:06

If you use bash, just:

shopt -s globstar # to match files recursively
cat -- **/*.tcl
  • I will have to go do some research on that, I like it, thanks! Commented Oct 28, 2015 at 21:48
  • Note that bash copied that from ksh93 which copied it from zsh. Commented Oct 28, 2015 at 22:06
  • Contrary to find, that will exclude hidden files and files in hidden directories. Use cat -- **/*(D) in zsh to include them. Commented Oct 28, 2015 at 22:07

You can run a command for each line of the input with a while loop and the shell built-in command read:

find . -name '*.tcl' | while IFS= read -r filename; do cat "$filename"; done

For simplicity's sake my preference for this would be the clean and readable solution already posed by Mark Komarinski. However, there are lots of ways to do this in Bash. Another interesting way is to avoid cat altogether and take advantage of redirection:

find . -name '*.tcl' -exec sh -c 'printf "%s\n" "$(< {})' \;;

In fact, if you want to accomplish the whole task purely with Bash built-ins then you can combine the use of redirection with the glob solution posed by chaos:

shopt_globstar_temp="$(shopt -p globstar)";
shopt -s globstar;
for filename in **/*.bat; do
    printf "%s" "$(< "${filename}")";

These are a bit convoluted, but my point here is to illustrate that Bash can do some powerful things with file descriptors and redirection. There are often many solutions to a given problem.

I don't understand why I have to cat a filename before cat will see it as a file instead of a string though....

The output of cat /dev/fd/stdin will be the same as the output of find in your last example, so it will effectively be replaced by <filename1>.tcl <filename2>.tcl ... and cat use that file list as its list of arguments.

If you're wondering why you have to cat stdin in that same example, the reason is that not all programs treat stdin the same way as they treat arguments. If data is transferred to cat through stdin then cat will simply output that same data instead of interpreting it as a filename to be read.

  • I guess that makes sense but it seems like you could just use echo if you wanted to merely print what was piped. Thanks! Commented Oct 29, 2015 at 17:29
  • You bet. printf is generally better than echo but it could certainly be replaced with echo here.
    – Mike Hill
    Commented Oct 29, 2015 at 21:17

You must log in to answer this question.

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