Bash (and similar) will replace !$ with the last parameter of the previous command.
This is a trick I’ve used lots when wanting to perform a non-piping operation on a command I’ve ‘cat’ed (eg ‘rm -v !$’)
I’d never criticise anyone for “useless” use of ‘cat’ though. If the fork() overhead was really that critical then it wouldn’t be a shell command to begin with.
Even easier, press alt+. (fewer keystrokes too! cli golf is fun), it'll copy-and-paste the last parameter from the previous command. If you press . multiple times it'll go further back into your history.
I look through a lot of logs, so I've aliased `not` to `grep -vE` and using the method you describe over several iterations i end up with a history that has a lot of
cat log | not spam1 | not spam2 | not 'spam(3|4)' | .... | less
What's really useless are all the "useless use of cat" comments (and shellcheck warnings although I take it for a shell script there could be cases where one less process may be justified [although really if you're at that point you've got other things to worry than cat, sadly]).
I use "cat ... | ... | ... " like in TFA and just like many in this thread because it simply makes sense. It's more intuitive. It's easier to read. It requires less braincycles to remember how this or that command wants its parameter passed, etc.
I think the "useless use of cat" movement made its time: it failed. Many of us are never going to give up our use(less|ful) of cat (you decide). So stop wasting your time complaining about it.
I use shellcheck a lot, and I've found so far that its complaints fall neatly into one of two buckets: "oh, whoops, nice catch" and "shut up, shellcheck".
"useless use of cat" goes into the latter bucket. Complaining at me about it does not actually improve the code; it's just a nag about a bad habit that, arguably, isn't even a bad habit.
I think you're mistaken here, and confusing two different usages of cat.
"Useless" uses of cat aren't bad habits during interactive usage, for all the reasons people mention here which I won't rehash.
For scripts, however, the story is different than for one-off commands. For one thing, it's slower due to the extra forks and copying of data across pipes, so there's at least that. For another, it prevents the command from inspecting the other end of its pipe, which can negatively impact usage in some case. (For example, if the program knows its input is from a terminal, it may flush its output on every newline it sees.) Moreover, a bunch of the arguments for the interaction case (like "it's fewer keystrokes" or whatever) don't even apply to the script case in the first place...
The end result here is that you definitely shouldn't assume some habit is just fine with scripting merely because it's fine when you're typing on the terminal, or vice-versa.
The concept presented is something I can agree with in principle, but "transforming a filename into the content of the file" is a really thin justification for a responsibility.
By all means don't build something where you have cascading effect and need to retest an entire pipeline, but this is _not_ it.
P.S.
And if you really really want to keep it separate, just do "< access.log head -500 etc etc etc" (no I didn't forget a pipe. And yes the "< inputfile" works even if it's in front of what you're calling).
> And if you really really want to keep it separate, just do "< access.log head -500 etc etc etc" (no I didn't forget a pipe. And yes the "< inputfile" works even if it's in front of what you're calling).
Or just use `cat` and let the pipe separate the different steps. "< access.log head" is nice but it breaks this representation where each step is piped into the next one. Sure, once you’re done fiddling you can rewrite the thing to remove the "cat", but when you are constructing the thing I find it clearer to use cat.
> "transforming a filename into the content of the file" is a really thin justification for a responsibility.
Uh... I dunno, but my lizard brain thinks that the whole idea of mediating filesystem operations on storage and IPC mechanisms like pipes is a lot more complicated, magic, and deserving of a single command than merely filtering the data on stdin.
I agree with the article and the logic, and think this historic meme was basically wrong originally. You string up your chain of pipelines with the first element being "where does it come from?" and not merely whatever the first operation happens to be just because that operation allows for some kind of file input or redirection syntactically.
> transforming a filename into the content of the file" is a really thin justification for a responsibility
This is one of those things where I think it is until it isn't.
I sometimes second-guess myself when I think I might be over-single-responsibilifying. "Well in practice these two things are so trivial that this feels a little silly."
It often turns out to have been a good call in hindsight, especially when working with other people who aren't necessarily thinking about these things at all. If the responsibilities have been sufficiently split up, they're more likely to change only the part that needed to be changed, and less likely to complectify the two things together that really shouldn't've been. Or when I go "oh wow that thing that I thought I overly-abstracted sure composes well with this unexpected new thing!"
Hardcore separation of concerns is just another method of defensive programming.
> < access.log head -500 etc etc etc
It's too bad that the syntax is so different. Why does the first stage not end with "|"? There's space for shell syntax improvements, here. Maybe a 'cat'-like builtin that translates `cat foo | bar` into `bar <foo` so you can have the nice syntax but don't needlessly create processes would leave everyone happy.
I definitely think if you want to use `cat` then just go ahead, it's fine. Sometimes these things are a power play, a way to distinguish between people who know the social codes and those who don't. In this case, it probably had a reasonable origin even if it's now more of a way to beat on newcomers. On old systems, memory was limited, disk was slow and forking was expensive. Saving a process in a script or one liner was a noticeable improvement performance-wise.
I learned some bash from an old-timer who would write an infinite-loop like this:
while :; do
# loop body here
done
This works because the `:` is a way to set a label, and it implicitly returns 0. It's just a weird wrinkle of the language. So, why not do `while true`? On old systems, `true` was not a builtin and would call `/usr/bin/true`. Writing the loop this way saves a process fork on each iteration.
On a modern system, you'd be hard pushed to measure the difference, so it really doesn't matter which style you prefer.
No need to ask for a source. The word "label" in the POSIX shell documentation only occurs in the description of `case`, and it doesn't happen in the manual page for bash, dash, zsh, etc.
I'm in this camp as well, starting with input file redirection just makes so much sense to me.
</proc/0/environ xargs -0
I also don't tend to want enormous volumes of text in my terminal scrollback so I generally view files or pipe verbose commands to `less`, then when I find what I want to send to the terminal I use the `|` less command to pipe it to `cat`.
Or to grab just a few lines for my later reference:
kubectl get po/my-pod -o yaml | less
/* find the lines I'm interest in */
-N
|^sed -n 34,35p
I have an old note named the same as this blog post:
cat /dev/sdb > backup.img # make a disk image
cat /dev/sdb > /dev/sdc # clone disk
cat ~/Downloads/* # play Russian roulette with your terminal
cat > file # minimalistic text editor, ^D to exit saving, ^C to exit erasing the file
cat << wq > file # nearly complete emulation of ed
grep -r bongo . | cat # shorter than typing --color=never
cat -v file # cause 20 points of damage to wizards of bell labs
cat file > file # empty a file without removing the file
cat meow meow > meows # duplicate file contents
There is another reason: to make sure the program doesn't do anything funny with the file, like modifying it. I know it won't happen with "head" but for commands I am not familiar with, it is a way to be sure. And example of a command that does "something funny" is gunzip. With just a file as an argument, it will decompress the .gz file and erase the original instead of reading it and dumping its content to stdout.
I usually prefer to do "< file command" though. "cat" adds an extra layer of indirection, forcing stream processing and hiding the original file, but that's usually unnecessary. If you really don't trust the program, it is not an adequate solution anyways.
If you want to hide the file's device and inode number from the program that will be consuming the file's contents, then, yes, using cat makes sense. I've never had to do that. Just redirecting stdin is enough.
This is a trick I’ve used lots when wanting to perform a non-piping operation on a command I’ve ‘cat’ed (eg ‘rm -v !$’)
I’d never criticise anyone for “useless” use of ‘cat’ though. If the fork() overhead was really that critical then it wouldn’t be a shell command to begin with.
Then I can look at it before hitting return
useful shorthand
> sudo !!
for example.
Again, bash specific.
https://zsh.sourceforge.io/Doc/Release/Redirection.html#Redi...
UUoC criticism, to me, belongs when one sits down to script.
cat “filename.txt”
Up | grep “thing”
Up | grep -v “not thing”
Up | grep -v “other thing”
Etc. it’s just easier to build this way even if the initial cat is unnecessary.
> grep “thing I want” filename.txt.
…every time
Deleted Comment
I use "cat ... | ... | ... " like in TFA and just like many in this thread because it simply makes sense. It's more intuitive. It's easier to read. It requires less braincycles to remember how this or that command wants its parameter passed, etc.
I think the "useless use of cat" movement made its time: it failed. Many of us are never going to give up our use(less|ful) of cat (you decide). So stop wasting your time complaining about it.
"useless use of cat" goes into the latter bucket. Complaining at me about it does not actually improve the code; it's just a nag about a bad habit that, arguably, isn't even a bad habit.
"Useless" uses of cat aren't bad habits during interactive usage, for all the reasons people mention here which I won't rehash.
For scripts, however, the story is different than for one-off commands. For one thing, it's slower due to the extra forks and copying of data across pipes, so there's at least that. For another, it prevents the command from inspecting the other end of its pipe, which can negatively impact usage in some case. (For example, if the program knows its input is from a terminal, it may flush its output on every newline it sees.) Moreover, a bunch of the arguments for the interaction case (like "it's fewer keystrokes" or whatever) don't even apply to the script case in the first place...
The end result here is that you definitely shouldn't assume some habit is just fine with scripting merely because it's fine when you're typing on the terminal, or vice-versa.
By all means don't build something where you have cascading effect and need to retest an entire pipeline, but this is _not_ it.
P.S.
And if you really really want to keep it separate, just do "< access.log head -500 etc etc etc" (no I didn't forget a pipe. And yes the "< inputfile" works even if it's in front of what you're calling).
Or just use `cat` and let the pipe separate the different steps. "< access.log head" is nice but it breaks this representation where each step is piped into the next one. Sure, once you’re done fiddling you can rewrite the thing to remove the "cat", but when you are constructing the thing I find it clearer to use cat.
Or just… don’t?
No it doesn't?
is completely valid, and reads right-to-left as well as the cat version. IMO using stdin is preferable to either solution in TFA.Uh... I dunno, but my lizard brain thinks that the whole idea of mediating filesystem operations on storage and IPC mechanisms like pipes is a lot more complicated, magic, and deserving of a single command than merely filtering the data on stdin.
I agree with the article and the logic, and think this historic meme was basically wrong originally. You string up your chain of pipelines with the first element being "where does it come from?" and not merely whatever the first operation happens to be just because that operation allows for some kind of file input or redirection syntactically.
This is one of those things where I think it is until it isn't.
I sometimes second-guess myself when I think I might be over-single-responsibilifying. "Well in practice these two things are so trivial that this feels a little silly."
It often turns out to have been a good call in hindsight, especially when working with other people who aren't necessarily thinking about these things at all. If the responsibilities have been sufficiently split up, they're more likely to change only the part that needed to be changed, and less likely to complectify the two things together that really shouldn't've been. Or when I go "oh wow that thing that I thought I overly-abstracted sure composes well with this unexpected new thing!"
Hardcore separation of concerns is just another method of defensive programming.
> < access.log head -500 etc etc etc
It's too bad that the syntax is so different. Why does the first stage not end with "|"? There's space for shell syntax improvements, here. Maybe a 'cat'-like builtin that translates `cat foo | bar` into `bar <foo` so you can have the nice syntax but don't needlessly create processes would leave everyone happy.
I learned some bash from an old-timer who would write an infinite-loop like this:
This works because the `:` is a way to set a label, and it implicitly returns 0. It's just a weird wrinkle of the language. So, why not do `while true`? On old systems, `true` was not a builtin and would call `/usr/bin/true`. Writing the loop this way saves a process fork on each iteration.On a modern system, you'd be hard pushed to measure the difference, so it really doesn't matter which style you prefer.
Do you have a source for that? I thought it was just POSIX built-in for true. Like `.` vs. `source`. What's a label in this context anyway?
Nope. Unix shell doesn't have labels (are you mixing with DOS batch files?).
: is a shell builtin that does nothing. In the bash man page, look for the first entry of the "SHELL BUILTIN COMMANDS" section. https://www.gnu.org/software/bash/manual/html_node/Bourne-Sh...
Or to grab just a few lines for my later reference:
[1] https://evalapply.org/posts/shell-aint-a-bad-place-to-fp-par...
`Y x | Z` is Verb-Subject-Object.
That's why I prefer using cat "uselessly".
I.e.
? that's... something else entirely.I usually prefer to do "< file command" though. "cat" adds an extra layer of indirection, forcing stream processing and hiding the original file, but that's usually unnecessary. If you really don't trust the program, it is not an adequate solution anyways.