How can I pipe output into another command? - bash

I have a script located at /usr/local/bin/gq which is returned by the command whereis gq, well almost. What is actually returned is gq: /usr/local/bin/gq. But the following gives me just the filepath (with some white space)
whereis gq | cut -d ":" -f 2
What I’d like to do is be able to pipe that into cat, so I can see the contents. However the old pipe isn’t working. Any suggestions?

If you want to cat the contents of gq, then how about:
cat $(which gq)
The command which gq will result in /usr/local/bin/gq, and the cat command will act on that.

Related

Can't properly print file in Bash

I'm trying to echo the contents of this link and it exhibits what to me is bizarre behavior.
git#gud:/home/git$ URL="https://raw.githubusercontent.com/fivethirtyeight/data/master/births/US_births_1994-2003_CDC_NCHS.csv"
git#gud:/home/git$ content=$(wget $URL -q -O -)
git#gud:/home/git$ echo $content
2003,12,31,3,12374_month,day_of_week,births
I expected this code to print the contents as I see them when I open the link on a browser. But instead, the output, on its entirety, is 2003,12,31,3,12374_month,day_of_week,births, that's it.
I actually see this behaviour locally as well, after downloading the file. Tried it both using curl and simply copy and pasting into a text editor and saving the file. They all exhibit the same behavior. The same happens with cat, cut, head, tail and even awk.
This doesn't happen with other files and works fine on Python. What am I missing? How do I get it to work?
I realize that the file doesn't end with a new line character, but adding it doesn't fix it.
I'm on Ubuntu 18.04.1 LTS and the CLI I'm using is Bash release 4.4.19(1).
The data file uses Mac-style end-of-line markers (carriage return only). When you echo the content, or just cat the file, the lines are all printing over eachother. If you were to view the file with less or vim, you would see the complete content.
Try this:
$ URL="https://raw.githubusercontent.com/fivethirtyeight/data/master/births/US_births_1994-2003_CDC_NCHS.csv"
$ curl -o data.csv "$URL"
The wc command thinks that the file has zero lines:
$ wc -l data.csv
0 data.csv
Now let's translate those end-of-line markers:
$ tr '\r' '\n' < data.csv > data-modified.csv
wc now sees a more reasonable number of lines:
$ wc -l data-modified.csv
3652 data-modified.csv
And if we were to cat the file:
$ cat data-modified.csv
.
.
.
2003,12,28,7,7645
2003,12,29,1,12823
2003,12,30,2,14438
2003,12,31,3,12374

Paste the last output and edit it in bash

I like to use bash (on linux) without touching mouse.
I often encounter the following situation.
$ locate libfreetype.a
/usr/lib/x86_64-linux-gnu/libfreetype.a
$ cd /usr/lib/x86_64-linux-gnu
In this case, I copy /usr/lib/x86_64-linux-gnu/ and paste it using mouse or type it. I do not want to do that.
Ideally, the output of locate libfreetype.a is stored in somewhere (maybe in killring??) and paste it with C-y command and edit it on terminal.
Are there good way to do this?
(Just for this example case, there are smart one-line commands. But those are not the desired answers. I want a general solution.)
Another example
Suppose that I remember that there is a memo... in the same directory as libfreetype.a but I forgot the directory name.
$ locate libfreetype.a
/usr/lib/x86_64-linux-gnu/libfreetype.a
$ nano /usr/lib/x86_64-linux-gnu/memo # Tab completion here
$ nano /usr/lib/x86_64-linux-gnu/memo_xxx.txt
if I could cache the output /usr/lib/x86_64-linux-gnu/libfreetype.a and paste it, things are very easy.
(nano $(dirname $(locate libfreetype.a))/memo_xxx.txt works for this case, but if I want to change the path itself, I need to think another technique.)
As noted in the comments, there probably no common way to do this in terminal. But it's possible to redirect the output of the command to program that copy stdin to clipboard, e. g. xclip. If you want to insert and edit copied text in terminal, you need to remove newline characters before copying. Consider following script:
copy.bash
#!/bin/bash
tr '\n' ' ' | xclip
Usage:
$ locate libfreetype.a | copy
$ cd # now press <shift> + <insert>
$ cd /usr/lib/x86_64-linux-gnu/libfreetype.a # continue editing
The xclip command copies its input for pasting into X applications.
The tr '\n' ' ' command translates all newlines into spaces. You need this if you want to paste the text into command line. It strips the trailing newline and joins lines if output contains more than one. If use plain xclip all newline characters are pasted literally, which causes bash to run command immediately after pasting and doesn't allow to edit it.
If output of the command (e. g. locate) is multi-line and you want to choose only one of them to copy (instead of copying all), you can use iselect. iselect reads input and shows the interactive menu for selecting a line/lines and prints it to the standart output.
Use it like this:
$ locate pattern | iselect -a | tr '\n' ' ' | xlip
# locate prints several lines
# iselect allows user to select one line interactively
# the result is copied to clipboard
$ # <shift> + <insert>
This also can be a script:
icopy.bash
#!/bin/bash
iselect -am | tr '\n' ' ' | xclip
(the -m option allows to choose several lines instead of one)
Usage:
$ locate pattern | icopy
Disadvantages of these approaches:
it works only with X sessions since xlcip need the X session to be running
you need to install new software (xclip and, optionally, iselect)
you need to redirect output explicitly, before running the command; so, technically, it cannot be considered as answer. But it is the best solution I have found for myself.
BTW, here is the script on my local machine that I really use quite often:
$ cat ~/bin/copy
#!/bin/bash
paste -sd\ | tr -d '\n' | xsel --clipboard
echo "Copied: $(xsel --clipboard --output)" >&2
$ echo hello | copy
Copied: hello
Links: man iselect, man xclip, man tr, yank.
You can run script (man script) from your .bashrc which generates a live log of your session's output. And bind a shortcut for opening the log file in an editor, so you can insert yanked text back into $READLINE_LINE.
But script captures raw output from interactive programs (such as editors), so if script could be modified to skip interactive output, it would work. Next step would be parsing output, to make navigation faster.
Here is a .bashrc snippet that does this for non-interactive tools only: https://asciinema.org/a/395092
I noticed that a solution to this problem is given by a terminal emulator kitty. We can use a feature called "hints" and keyboard shortcuts configured by default.
As in the original question, let's think of the situation.
$ locate libfreetype.a
/usr/lib/x86_64-linux-gnu/libfreetype.a
$ # you want to input /usr/lib/x86_64-linux-gnu here
If you are using kitty, you can type ctrl+shift+p and then l.
You will enter a mode to select a line from the screen. When you can select the previous line, it is pasted into the current terminal input.
The detail if found in the official documentation.
The configuration associated with the action is written like this.
map ctrl+shift+p>l kitten hints --type line --program -
This means that kitten hints --type line --program - is the command mapped from ctrl+shift+p followed by l.
you could use
!$
like
shell$ echo myDir/
myDir/
shell$ cd !$
cd myDir/
shell$ pwd
/home/myDir

GNU 'ls' command not outputing the same over a pipe [duplicate]

This question already has answers here:
Why does ls give different output when piped
(3 answers)
Closed 6 years ago.
When I execute the command ls on my system, I get the following output:
System:~ user# ls
asd goodfile testfile this is a test file
However, when I pipe ls to another program (such as cat or gawk), the following is output:
System:~ user# ls | cat
asd
goodfile
testfile
this is a test file
How do I get ls to read the terminal size and output the same over a pipe as it does when printing directly to the terminal?
This question has been solved.
Since I'm using bash, I used the following to achieve the desired output:
System:~ user# ls -C -w "$(tput cols)" | cat
Use ls -C to get columnar output again.
When ls detects that its output isn't a terminal, it assumes that its output is being processed by some other process that wants to parse it, so it switches to -1 (one-entry-per-line) mode to make parsing easier. To make it format in columns as when it's outputting directly to a terminal, use -C to switch back to column mode.
(Note, you may also have to use --color if you care about color output, which is also normally suppressed by outputting to a pipe.)
Maybe -x "list entries by lines instead of by columns" with possible -w "assume screen width instead of current value" is what you need.
When the output goes to a pipe or non-terminal, the output format is like ls -1. If you want the columnar output, use ls -C instead.
The reason for the discrepancy is that it is usually easier to parse one-line-per-file output in shell scripts.
Since I'm using bash, I used the following to achieve the desired output:
System:~ user# ls -C -w "$(tput cols)" | cat

wc output differs inside/outside vim

I'm working on a text file that contains normal text with LaTeX-style comments (lines starting with a %). To determine the non-comment word count of the file, I was running this command in Bash:
grep -v "^%" filename | wc -w
which returns about the number of words I would expect. However, if from within vim I run this command:
:r! grep -v "^%" filename | wc -w
It outputs the word count which includes the comments, but I cannot figure out why.
For example, with this file:
%This is a comment.
This is not a comment.
Running the command from outside vim returns 5, but opening the file in vim and running the similar command prints 9.
I also was having issues getting vim to prepend a "%" to the command's output, but if the output is wrong anyways, that issue becomes irrelevant.
The % character is special in vi. It gets substituted for the filename of the current file.
Try this:
:r! grep -v "^\%" filename | wc -w
Same as before but backslash-escaping the %. In my testing just now, your example :r! command printed 9 as it did for you, and the above printed 5.

Bash: piped argument to open command fails. Open commands excutes too early?

I'm pretty much a novice to shell scripting. I'm trying to send the output of some piped commands to an open command in bash in OSX.
My ultimate goal is to compile a Flex/Actionscript application from TextWrangler by calling a bash script with a little Applescript and have the result played directly in a Flash Player. The Applescript is pretty much doing it's job. But the bash script doesn't work as I expect. Same results when I ommit the Applescript and simply put it directly in terminal.
This is what the Applescript is sending to terminal:
mxmlc -warnings=false DocumentClass.as | tail -n 1 | sed 's/[[:space:]].*$//' | open -a 'Flash Player'
So basically, I read the last line of the output of mxmlc, which usually looks something like this:
/Users/fireeyedboy/Desktop/DocumentClass.swf (994 bytes)
and I strip everything after the first space it encounters. I know it's hardly bulletproof yet, it's still just a proof of concept. When I get this roughly working I'll refine. It returns the desired result so far:
/Users/fireeyedboy/Desktop/DocumentClass.swf
But as you can see, I then try to pipe this sed result to the Flash Player and that's where it fails. The Flash Player seems to open way too early. I would expect the Flash Player to open only after the script finished the sed command. But it opens way earlier.
So my question is twofold:
Is it even possible to pipe an
argument to the open command this
way?
Do I need to use some type
of delay command to get this
working, since the open command doesn't seem to be waiting for the input?
You're trying to give the name of the swf file as input to stdin of the open command, which it doesn't support.
It expects the file name as an argument (similar to -a).
You can do something like this:
FILENAME=`xmlc -warnings=false DocumentClass.as | tail -n 1 | sed 's/[[:space:]].*$//'`
open -a 'Flash Player' $FILENAME
or on a single line:
open -a 'Flash Player' `xmlc -warnings=false DocumentClass.as | tail -n 1 | sed 's/[[:space:]].*$//'`
If you're using bash (or another modern POSIX shell), you can replace the pretty unreadable backtick character with $( and ):
open -a 'Flash Player' $(xmlc -warnings=false DocumentClass.as | tail -n 1 | sed 's/[[:space:]].*$//')
All commands in a pipe are started at the same time. During this step, their input/outputs are chained together.
My guess is that open -a 'Flash Player' doesn't wait for input but simply starts the flash player. I suggest to try to run the player with an argument instead:
name=$(mxmlc -warnings=false DocumentClass.as | tail -n 1 | sed 's/[[:space:]].*$//')
open -a 'Flash Player' "$name"
I'm not familiar with the "open" command as it seems to be a mac thing, but i think what you want to do is:
open -a 'Flash Player' $(mxmlc -warnings=false DocumentClass.as | tail -n 1 | sed 's/[[:space:]].*$//')
In general you can't pipe arguments to a command, you have to specify that you want the output of the previous command to be treated as arguments, either as in my example or with the xargs command. Note that there is a limit on the maximum size of a command line, though.

Resources