Use pipe ("|") as first symbol in bash or zsh command - bash

I have two simple scripts:
./cpthat
BlueM/cliclick types on the keyboard: Shift+Cmd+A, then Cmd+C, to the active iTerm terminal:
#!/bin/zsh
cliclick kd:shift,cmd t:a ku:shift t:c ku:cmd
pbpaste>$THATF
Shift+Cmd+A selects the output from the previous command, and
Cmd+C copies "that" to the clipboard.
pbpaste then writes that to the file $THATF defined system-wide.
./that
#!/bin/zsh
cat $THATF
This prints out the output of the last command as stored by cpthat.
(I know I can run $ command > $THATF directly but for other reasons I need to act retroactively on the command output. Also, not thread safe.)
The challenge:
I'm trying to get to where I can start a zsh or bash command with a pipe:
$ |grep -i sometext
Where, in effect, this happens:
$ that|grep -i sometext
Would this be possible somehow?
Overriding the pipe operator?
zsh config magic?
I'm using zsh heavily but am open for any solution.

You don't need to start with a |. Thegrep utility naturally reads STDIN.
Here's a contrived example:
# /bin/sh
# count_matches
grep $1 | wc -l
$ cat file | count_matches thing
You can see the | you're looking for is on the command line itself, not within the script
Similarly this works:
$ count_matches thing < file
In the first example, the STDIN is connected (via the pipe) to the output of the first command (trivially cat). In the second, it's from the actual file via redirection.
So, just get rid of the | and you should be good to go.

Animation showing that | alone at the beginning of command can be replaced automatically by the output of previous command:
Edit ~/.zshrc to override zsh's zle accept-line widget:
readonly THATF="path/to/your/temporary/file"
my-accept-line () {
if [[ "${BUFFER:0:1}" == '|' ]]; then
/usr/local/bin/cliclick kd:shift,cmd t:a ku:shift w:100 t:c ku:cmd
pbpaste>"${THATF}"
BUFFER='cat ${THATF} '${BUFFER}
fi
zle .accept-line
}
zle -N accept-line my-accept-line
Explanation
When you hit enter after entering a command, zsh runs the accept-line widget.
We override that widget, but before exiting we remember to call the original widget with zle .accept-line. With the dot prefix the factory widget is ran.
In iTerm2, shift+cmd+a selects all the output from the previous command, and cmd+c copies that to the system pasteboard.
We paste the contents of the pasteboard and redirects that to the temporary file declared earlier, pointed to by ${THATF}.
We prepend $BUFFER, the zsh special variable available within zle widget code, with the output of the previous command.
Dependencies, caveats:
This particular solution depends on:
cliclick dispatching macOS keyboard events. Perhaps a native solutions exist e.g. ANSI/escape sequence.
iTerm to handle the keybind for copying last commands output.
zsh for the zle widget.
Xode snippet above is proof-of-concept only and is wildly insecure.

Related

Remove color and redirect ouptut of a bash script from within

My question is simple, how can I redirect all outputs of a bash script to file and terminal and remove color characters from within the script itself?
I can't find an answer which fits all my needs.
So far I tried tee to output to file and terminal, combined with 2>&1 to get stederr and stdout, sed to remove color characters and all of this with exec to do it from within my script but it doesn't work, I only get colored logs into the terminal and nothing in file.
#!/usr/bin/env bash
exec 2>&1 | sed -r 's/\x1b\[[0-9;]*m//g' | tee script.logs
python somepython.py
python someotherpython.py
Here the python scripts produce outputs which are colored.
I want to log those to terminal (untouched) and in file (without the color). In reality there is a lot more going on in my bash script than those just two python scripts, that's why I want to globally redirect the output of my script bash and not just pipe after each python script.
Thus I used exec because I though it allowed to redirect all output produce by a script.
Thanks in advance for any advice and help,
PS: I dont want colored logs in file but I don't care in terminal if this is what needs to be done for logs to not be colored in file.
You may put all your calls in a curly-braced group and redirect the whole lot, e.g.:
#!/usr/bin/env bash
{
python somepython.py
python someotherpython.py
} 2>&1 | sed -r 's/\x1b\[[0-9;]*m//g' | tee script.logs
This way, all stdout and stderr outputs will be passed along the filter.
Color the terminal, not the file
If you want to write the colors to the terminal and write the uncolored text to the file, you may apply the sed filter to the file written by tee, e.g. your script would look something like:
#!/usr/bin/env bash
{
python somepython.py
python someotherpython.py
} 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > script.logs)
This uses process substitution which is a very powerful tool in bash, albeit a bit difficult at first.
Remove buffering
Assuming you would like to read the contents as soon as possible, you may want to deactivate python’s block buffering. This can be done using the -u option:
#!/usr/bin/env bash
{
python -u somepython.py
python -u someotherpython.py
} 2>&1 | tee >(sed -r 's/\x1b\[[0-9;]*m//g' > script.logs)
Side Note: Beware of CSI
Not all special characters are based on the Control Sequence Initiator ESC [.
If a text is colored in green with tput setaf 1, it will use the control sequence ESC [31m, but if the color is reset with tput sgr0, the control sequence may be ESC (B ESC [m (please note the ESC (B sequence). So if you filter only the ESC [ sequences, you may still have control sequence waste in your log file.
Things get even worse if the program uses other types of control characters such as cursor commands.
For those reasons, the best way to avoid problems is to simply avoid writing control sequences from your python scripts. Most builtin programs are protected against that by checking if the output is a terminal before choosing to display colors or not. When the output is not a terminal it assumes that colors may cause issues.
With that said I don’t know if you have control over the python scripts (or other calls you might have), but if you do you might want to test if the output is a terminal. In Bash you check this way:
if [ -t 1 ] # does stdout end up on a terminal?
then
# Display fancy colors
else
# Minimalist display
fi
In Python it would be:
if sys.stdout.isatty(): # does stdout end up on a terminal?
# Display fancy colors
else:
# Minimalist display

Why doesn't "which myscript | xargs vim" work well?

Why doesn't
which myscript | xargs vim
work nicely? My terminal (ubuntu 14.04) freezes when I exit vim.
Or, is there an a nice clean alternative?
Why The Original Doesn't Work
You can't meaningfully pipe anything into vim, if you're going to use it as an interactive editor: A pipeline overrides stdin; an editor needs to be able to access your stdin (unless it's, say, interacting via X11 -- but that would be gvim).
To go into a little more detail: foo | bar runs both foo and bar at the same time, with the stdout of foo connected to the stdin of bar. Thus, which myscript | xargs vim has the shell originally starting two processes -- which myscript and xargs vim -- with the stdout of which myscript connected to the stdin of xargs vim.
However, this means that xargs vim is getting its input from which, and not from the terminal/console that the user was typing at. Thus, when xargs vim starts vim, the stdin which vim inherits isn't connected to the terminal either -- and vim, being an interactive editor built to get input from the user at a terminal, fails (perhaps spectacularly or entertainingly).
What To Do Instead
vim "$(which myscript)"
The $() syntax above is a command substitution, which is replaced with the stdout of the command which it runs. As such, while this overrides the stdout of which (directed into a FIFO which the shell reads from for purposes of that substitution), it does not in any respect redirect the input and output handed to vim.
Alternately, if you really want to use xargs (note the following uses -d, a GNUism, to ensure that it works correctly when passed filenames with spaces -- though not filenames with newlines):
which myscript | xargs -d $'\n' sh -c 'exec vim "$#" <&2'
The above has xargs, instead of directly running vim, start a shell which copies stderr (file descriptor 2) to stdin (file descriptor 0, the default target of redirection with <), and then starts vim, so as to provide that copy of vim a file descriptor for stdin that's attached to your terminal -- if your stderr isn't open to your TTY, replace <&2 with </dev/tty instead.

How can I easily log some specific command line commands into a file?

I often perform configuration changes using single line commands on Mac OS, Linux or even Windows and I want to easily log them in a file, so I can replay if I have to reconfigure the machine again.
Please not that I want to do these only for some commands, so the shell history is of not use.
Ideally I would like to be able to use some kind of shell extension that logs some of the commands.
As you know if you start your bash command with a space, this command is not logged into the history.
What if I can have another prefix that would do the opposite? Is there something there that can be used for this? A solution for bash would be more than enough and if there is an already existing solution it would much better than me writing a new one.
You could do your logging in PROMPT_COMMAND, extracting the specific commands from shell history and writing them to a file.
Something like:
log () {
last_command="$(history -p \!\!)"
if [[ $last_command == " "* ]] # save commands starting with *two* spaces
then
printf "%s\n" "$last_command" >> ~/special.log
fi
}
PROMPT_COMMAND="log; $PROMPT_COMMAND"
This has problems:
PROMPT_COMMAND is run each time the prompt is printed. Just pressing Enter multiple times could cause a command to be logged multiple times.
Marking with two spaces would, of course, need you to remove ignorespace or ignoreboth from HISTCONTROL so that commands starting spaces are logged at all.
AFAICT, history is updated when the next command is read, so the command is logged after the next command returns to the prompt, since that's when the correct history is available in PROMPT_COMMAND.
All this would be easier in zsh, with a preexec hook:
preexec () {
if [[ $1 == " "* ]]
then
printf "%s\n" "$1" >> ~/special.log
fi
}
The preexec function automatically gets the command as the first argument if history is enabled, saving us a deal of trouble. It is run when the command has been read, but before it begins execution, so the timing is perfect. From the documentation:
preexec
Executed just after a command has been read and is about to be
executed. If the history mechanism is active (regardless of whether
the line was discarded from the history buffer), the string that the
user typed is passed as the first argument, otherwise it is an empty
string. The actual command that will be executed (including expanded
aliases) is passed in two different forms: the second argument is a
single-line, size-limited version of the command (with things like
function bodies elided); the third argument contains the full text
that is being executed.
$ ls
$ echo foo | echo bar
bar
$ cat ~/special.log
ls
echo foo | echo bar
A function in .bashrc can be used like a prefix:
log_this_command () {
echo "$#" >> ~/a_log_file # log the command to file
"$#" # and run the command itself
}
Caveat: this only logs expanded arguments, rather than the raw input.
Source function with the same name function screencapture {echo "used parms: $#"; command screencapture $#}
appending to log file function screencapture {echo "$(date) screencapture " $# >> ~/log.txt; command screencapture $#}
as one runs screencapture command, log entry is created and command executes as uninterfered
you could automate in creating these functions, if the list of them is like .... all of them

Read selected text in vim

I have the following bash script (play.bash):
#!/bin/bash
pico2wave -l=en-US -w=/tmp/test.wav "$1"
aplay /tmp/test.wav
rm /tmp/test.wav
I would like to use it for reading selected text in the vim editor.
The following does not work
:'<,'>w !bash play.bash
The command executes, but I'm not hearing it.
I'd like to only hear the text, without leaving the vim window.
Is this possible?
When using the ! command in vim, your selection will be piped to your script on standard input, not given as a command line argument.
A simple solution would be to modify your script so it reads from standard input:
xargs pico2wave -l=en-US -w=/tmp/test.wav
xargs reads from standard input, then calls pico2wave with the data as command line arguments, removing newlines.

Copy current command at bash prompt to clipboard

I would like a quick keyboard command sequence to copy the current command at a bash prompt to the clipboard.
So that, for example, to copy the last bash command to the clipboard, I'd press up+[some command sequence] to copy it. Or, for example, to search for a command in bash hisory, I'd use ctrl+r, search, display it on the command prompt, and then [some command sequence] to copy it, etc.
My current solution is using bash pipes: Pipe to/from the clipboard
So, to copy the previous command to clipboard:
echo "!!" | pbcopy
Which isn't too terrible, but what if the command to copy isn't the last command, etc.
What's the proper way to achieve what I'm trying to achieve here?
Taking #Lauri's post for inspiration, here's a solution using the bind command:
bind '"\C-]":"\C-e\C-u pbcopy <<"EOF"\n\C-y\nEOF\n"'
ctrl-] then will copy whatever is on the current bash prompt to the clipboard.
To make it persistent, you can add the bind command as above to your ~/.bashrc, or you can strip off the outer quotes and remove the 'bind' part of the call and add the result to your ~/.inputrc.
Non-OS-X users will have to swap pbcopy out with the appropriate command, probably xclip.
A quoted heredoc was used instead of a an echo+pipe technique so that both single and double quotes in the command at the bash prompt are preserved. With this technique, for example, I was able to hit ctrl-], copy the actual bind command from the terminal prompt, and paste it here in the answer. So the heredoc technique handles all of the special characters in the bind command here.
You can use READLINE_LINE with bind -x in bash 4:
copyline() { printf %s "$READLINE_LINE"|pbcopy; }
bind -x '"\C-xc":copyline'
You can install bash 4 and make it the default login shell by running brew install bash;echo /usr/local/bin/bash|sudo tee -a /etc/shells;chsh -s /usr/local/bin/bash.
I also use this function to copy the last command:
cl() { history -p '!!'|tr -d \\n|pbcopy; }
I spent a decent amount of time today writing a simple zsh implementation for macOS; usage is as follows:
example command: git commit -m "Changed a few things"
command that copies: c git commit -m "Changed a few things"
# The second command does not actually execute the command, it just copies it.
# Using zsh, this should reduce the whole process to about 3 keystrokes:
#
# 1) CTRL + A (to go to the beginning of the line)
# 2) 'c' + ' '
# 3) ENTER
preexec() is a zsh hook function that gets called right when you press enter, but before the command actually executes.
Since zsh strips arguments of certain characters like ' " ', we will want to use preexec(), which allows you to access the unprocessed, original command.
Pseudocode goes like this:
1) Make sure the command has 'c ' in the beginning
2) If it does, copy the whole command, char by char, to a temp variable
3) Pipe the temp variable into pbcopy, macOS's copy buffer
Real code:
c() {} # you'll want this so that you don't get a command unrecognized error
preexec() {
tmp="";
if [ "${1:0:1}" = "c" ] && [ "${1:1:1}" = " " ] && [ "${1:2:1}" != " " ]; then
for (( i=2; i<${#1}; i++ )); do
tmp="${tmp}${1:$i:1}";
done
echo "$tmp" | pbcopy;
fi
}
Go ahead and stick the two aforementioned functions in your .zshrc file, or wherever you want (I put mine in a file in my .oh-my-zsh/custom directory).
If anyone has a more elegant solution, plz speak up.
Anything to avoid using the mouse.
If xsel is installed on your system you can add this in .inputrc :
C-]: '\C-e\C-ucat <<"EOF" | tr -d "\\n" | xsel -ib\n\C-y\nEOF\n'
Alternatively, if xclip is installed you could add this:
C-]: '\C-e\C-ucat <<"EOF" | tr -d "\\n" | xclip -se c\n\C-y\nEOF\n'
Notice: Used code from #Clayton's answer.
I use history to find the command number that I am looking for, then I do:
echo "!command_number" | xclip -in
$ history | cut -c 8- | tail -1 | pbcopy
or in .zshrc file add an alias
alias copy='history | cut -c 8- | tail -1 | pbcopy'

Resources