In Bash, I'd like to create a binding in my .inputrc that makes use of the !! built-in to repeat the last command. But this doesn’t seem to properly expand the !!
bind -x '"\C-t": echo $(!!)'
When I invoke the above binding (Ctrl+t) I just get this:
-bash: !!: command not found
Likewise the simpler
bind -x '"\C-t": echo !!'
Just yields
!!
Instead of the actual command.
Obviously my real use-case is more substantive than this example, this is just an illustration of the problem.
Edit:
This question has nothing to do with echo "#!" fails -- "event not found" which it is claimed mine is a duplicate of. That question pertains to a generalized failure of !* expansion in regular bash due to quoting issues or lack of history. My question on the other hand is very specific to the context of being used inside an .inputrc file (or perhaps an alias), where a different set of factors come into play. On my regular command line, the so-called "bang expansions" have always worked fine. It's only in these special contexts where the problems arose, and hence led to this question.
"\C-t": "fc -s\n"
fc -s re-executes the last command, and fc is also a builtin:
$ type fc
fc is a shell builtin
Well it seems there's something odd going on with shell quoting.
This does not work
bind -x '"\C-l": "!! \n"'
But putting this in .inputrc does
"\C-l": "!! \n"
Don't really understand why the former doesn't work, but at least the latter does.
EDIT: OK got it. Apparently the "-x" isn't needed when defining on the cmd line. So we can simply write
bind '"\C-l": "!! \n"'
Using -x tells bind not to interfere with what you've already typed (almost like a primal ncurses approximation of a modal window!) which is not what I'm after. Thanks to the accepted answer on In bash, how do I bind a function key to a command? for this insight!
Related
In a vanilla vim on Mac, when I type :set grepprg?, it returns the following:
grepprg=grep -n $* /dev/null.
I understand what -n and /dev/null means,
thanks to an old question here.
I also understand what $ and * means individually.
However, I am not sure what to make of $*.
I tried to look it up in the vim doc,
but all that I could find was
The placeholder "$*" is allowed to specify where the arguments will be included.
I sense that I am missing some important connection here.
I would really appreciate if someone could explain to me
how $* works as a placeholder.
Update:
Thanks to the detailed explanation from #romainl,
I realized that I was misinterpreting $* as regex,
whereas they are part of the convention in shell script.
In fact, there already exists old post
about this particular convention.
Silly me!
I'm not sure what kind of explanation is needed beyond what you have already quoted:
The placeholder "$*" is allowed to specify where the arguments will be included.
$* is just a placeholder and it works like all placeholders: before being actually sent to the shell, the command is built out of &grepprg and $*, if present, is replaced by any pattern, filename, flags, etc. provided by the user.
Say you want to search for foo\ bar in all JavaScript files under the current directory. The command would be:
:grep 'foo\ bar' *.js
After you press <CR>, Vim grabs any argument you gave to :grep, in this case:
'foo\ bar' *.js
then, if there is a $* in &grepprg, it is replaced with the given argument:
grep -n 'foo\ bar' *.js /dev/null
or, if there is no $* in &grepprg, the given argument is appended to it, and only then sends the whole command to a shell.
$* means "in this command, I specifically want the user-provided arguments to appear here".
As for the meaning of $*… $ and * have no intrinsic meaning and $* could have been $$$PLACEHOLDER$$$ or anything. $* may have been chosen because it is used in shell script to represent all the arguments given to a function or script, which is somewhat close in meaning to what is happening in &grepprg with $*.
In following some random directions on the internet, trying to debug an issue of mine at a shell (I use zsh), I ran set -x. Thanks to this I figured out my issue. However, I'm now in an awkward position of not knowing how to turn this debugging off -- I really don't even understand what I did in the first place, you see.
I also figured out that I could just do zsh and get a new shell. The obvious unset -x does not work. I would like to know the correct way. Thanks!
Update:
Found this unix&linux stack exchange post about what -x does. Still don't know how to turn it off.
You can use set +x to switch it back. The output of help set describes this:
$ help set
set: set [--abefhkmnptuvxBCHP] [-o option] [arg ...]
...
-v Print shell input lines as they are read.
-x Print commands and their arguments as they are executed.
...
Using + rather than - causes these flags to be turned off. The
flags can also be used upon invocation of the shell. The current
set of flags may be found in $-. The remaining n ARGs are positional
parameters and are assigned, in order, to $1, $2, .. $n. If no
ARGs are given, all shell variables are printed.
Note the “Using + rather than - causes these flags to be turned off” part.
As a terminal noob, I've created aliases for pretty much everything I do. The problem is that I've started forgetting those few commands that I do know because of it. On top of that, I sometimes need to edit a variable in the previous command.
So what I'd like is if when I use an alias, the first line printed is the actual command it represents, then proceed to execute the command. Since pressing up and !! simply reprints the alias, I'm not too sure how to get a reference to the underlying command.
Thanks.
You can always use:
alias to list all your aliases, or
alias name to show the specification of the alias name.
So in fact you can define your alias as
alias myalias="alias myalias; <do stuff>"
I also think that chepner's answer with Alt-Control-e is more practical, but I am posting this for completeness.
After you type your alias, but before you hit enter, you can type Meta-Control-e (probably Alt-Control-e, but possibly Esc-Control-e) to expand what you've typed; this will expand any aliases and history expansions so you can see the "long" form of what you've typed.
You can use variables in your alias to get a clean output:
alias test='_TMP="pushd /home/a/b/c";echo $_TMP; eval $_TMP'
If you want to avoid messages coming out of alias, you can do
_TMP="pushd /home/a/b/c >/dev/null 2>&1"
It does add an environment var of _TMP.
The old post had error in it.
I use ZSH for my terminal shell, and whilst I've written several functions to automate specific tasks, I've never really attempted anything that requires the functionality I'm after at the moment.
I've recently re-written a blog using Jekyll and I want to automate the production of blog posts and finally the uploading of the newly produced files to my server using something like scp.
I'm slightly confused about the variable bindings/usage in ZSH; for example:
DATE= date +'20%y-%m-%d'
echo $DATE
correctly outputs 2011-08-23 as I'd expect.
But when I try:
DATE= date +'20%y-%m-%d'
FILE= "~/path/to/_posts/$DATE-$1.markdown"
echo $FILE
It outputs:
2011-08-23
blog.sh: line 4: ~/path/to/_posts/-.markdown: No such file or directory
And when run with what I'd be wanting the blog title to be (ignoring the fact the string needs to be manipulated to make it more url friendly and that the route path/to doesn't exist)
i.e. blog "blog title", outputs:
2011-08-23
blog.sh: line 4: ~/path/to/_posts/-blog title.markdown: No such file or directory
Why is $DATE printing above the call to print $FILE rather than the string being included in $FILE?
Two things are going wrong here.
Firstly, your first snippet is not doing what I think you think it is. Try removing the second line, the echo. It still prints the date, right? Because this:
DATE= date +'20%y-%m-%d'
Is not a variable assignment - it's an invocation of date with an auxiliary environment variable (the general syntax is VAR_NAME=VAR_VALUE COMMAND). You mean this:
DATE=$(date +'20%y-%m-%d')
Your second snippet will still fail, but differently. Again, you're using the invoke-with-environment syntax instead of assignment. You mean:
# note the lack of a space after the equals sign
FILE="~/path/to/_posts/$DATE-$1.markdown"
I think that should do the trick.
Disclaimer
While I know bash very well, I only started using zsh recently; there may be zshisms at work here that I'm not aware of.
Learn about what a shell calls 'expansion'. There are several kinds, performed in a particular order:
The order of word expansion is as follows:
tilde expansion
parameter expansion
command substitution
arithmetic expansion
pathname expansion, unless set -f is in effect
quote removal, always performed last
Note that tilde expansion is only performed when the tilde is not quoted; viz.:
$ FILE="~/.zshrc"
$ echo $FILE
~/.zshrc
$ FILE=~./zshrc
$ echo $FILE
/home/user42/.zshrc
And there must be no spaces around the = in variable assignments.
Since you asked in a comment where to learn shell programming, there are several options:
Read the shell's manual page man zsh
Read the specification of the POSIX shell, http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html, especially if you want to run your scripts on different operating systems (and you will find yourself in that situation one fine day!)
Read books about shell programming.
Hang out in the usenet newsgroup comp.unix.shell where a lot of shell wizards answer questions
I'm writing a script which needs to use the shell's brace expansion, but nothing I've tried works. For (a contrived) instance, say I have a variable containing the string
thing{01..02}
and I (obviously) want to expand it to
thing01 thing02
from inside the script, how can I do that?
(For anyone who assumes this is a duplicate of this other question, please read them more carefully. That question is regarding working from the shell, not a shell script, and doesn't require the ability to expand arbitrary expressions.)
$ echo thing{01,02}
thing01 thing02
Make sure that braceexpand is turned on with set -o braceexpand.