4

In a shell, I run following commands without problem,

ls -al

!ls

the second invocation to ls also list files with -al flag. However, when I put the above script to a bash script, complaints are thrown,

!ls, command not found.

how to realise the same effects in script?

Community
  • 1
  • 1
Richard
  • 14,642
  • 18
  • 56
  • 77
  • 5
    It would be better to just copy the previous line, and not rely on command history inside a script. – Henk Langeveld Aug 23 '12 at 18:45
  • is it possible to do this with different commands? For example mkdir + cd? – Adam Trhon Aug 23 '12 at 18:47
  • Although you can enable this, this is bad programming practice. It will make your scripts error-prone and hard to understand. There is a reason it is disabled by default. Why do you want to do this? – Michael Hoffman Aug 23 '12 at 21:11

3 Answers3

6

You would need to turn on both command history and !-style history expansion in your script (both are off by default in non-interactive shells):

set -o history
set -o histexpand

The expanded command is also echoed to standard error, just like in an interactive shell. You can prevent that by turning on the histverify shell option (shopt -s histverify), but in a non-interactive shell, that seems to make the history expansion a null-op.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • I had the same thought, but after testing, this doesn't provide working history expansion in a script. However, I can't imagine the use-case for this. History expansion is to ease interactive use. – jordanm Aug 23 '12 at 18:45
  • I made two mistakes; `set -H` is `set -o histexpand`, which you also need in addition to `set -o history`. – chepner Aug 23 '12 at 18:59
  • in bash4, it complaints ``histcommand`` is invalid... what is the use of it..? – Richard Aug 23 '12 at 20:28
0

Well, I wanted to have this working as well, and I have to tell everybody that the set -o history ; set -o histexpand method will not work in bash 4.x. It's not meant to be used there, anyway, since there are better ways to accomplish this.

First of all, a rather trivial example, just wanting to execute history in a script: (bash 4.x or higher ONLY)

#!/bin/bash -i
history

Short answer: it works!!
The spanking new -i option stands for interactive, and history will work. But for what purpose?

Quoting Michael H.'s comment from the OP:
"Although you can enable this, this is bad programming practice. It will make your scripts (...) hard to understand. There is a reason it is disabled by default. Why do you want to do this?"

Yes, why? What is the deeper sense of this? Well, THERE IS, which I'm going to demonstrate in the follow-up section.

My history buffer has grown HUGE, while some of those lines are script one-liners, which I really would not want to retype every time. But sometimes, I also want to alter these lines a little, because I probably want to give a third parameter, whereas I had only needed two in total before. So here's an ideal way of using the bash 4.0+ feature to invoke history:

$ history
 (...)
 <lots of lines>
 (...)
  1234 while IFS='whatever' read [[ $whatever -lt max ]]; do ... ; done < <(workfile.fil)
 <25 more lines>

So 1234 from history is exactly the line we want. Surely, we could take the mouse and move there, chucking the whole line in the primary buffer? But we're on *NIX, so why can't we make our life a bit easier? This is why I wrote the little script below. Again, this is for bash 4.0+ ONLY (but might be adapted for bash 3.x and older with the aforementioned set -o ... stuff...)

#!/bin/bash -i
[[ $1 == "" ]] || history | grep "^\s*$1" | 
awk '{for (i=2; i<=NF; i++) printf $i" "}' | tr '\n' '\0'

If you save this as xselauto.sh for example, you may invoke

$ ./xselauto.sh 1234

and the contents of history line #1234 will be in your primary buffer, ready for re-use! Now if anyone still says "this has no purpose AFAICS" or "who'd ever be needing this feature?" - OK, I won't care. But I would no longer want to live without this feature, as I'm just too lazy to retype complex lines every time. And I wouldn't want to touch the mouse for each marked line from history either, TBH. This is what xsel was written for.

BTW, the tr part of the pipe is a dirty hack which will prevent the command from being executed. For "dangerous" commands, it is extremely important to always leave the user a way to look before he/she hits the Enter key to execute it. You may omit it, but ... you have been warned.


P.S. This scriptlet is in fact a workaround, simulating !1234 typed on a bash shell. As I could never make the ! work directly in a script (echo would never let me reveal the contents of history line 1234), I worked around the problem by simply greping for the line I wanted to copy.

syntaxerror
  • 661
  • 2
  • 6
  • 24
  • You should probably use `!1234:p` to just reveal the contents of the item in history. There are also ways to _grep_ and match lines from history using `!?whatever?:p`, which would show you the content of that history item without executing it. – Evgeny Aug 08 '15 at 18:41
  • And an even better way to do what you are after is to enable `histverify`, so that commands from history are not executed when referred using `!` but rather just pasted into your prompt for you to execute. – Evgeny Aug 08 '15 at 18:43
-2

History expansion is part of the interactive command-line editing features of a shell, not part of the scripting language. It's not generally available in the context of a script, only when interacting with a (pseudo-)human operator. (pseudo meaning that it can be made to work with things like expect or other keystroke repeating automation tools that generally try to play act a human, not implying that any particular operator might be sub-human or anything).

twalberg
  • 59,951
  • 11
  • 89
  • 84
  • While I agree that it's not meant to be used in a script, it's not true that its not available. – chepner Aug 23 '12 at 19:07
  • That's why I said "not generally available" - you have to do some additional stuff to make it so... And even that depends on what shell - there are some where it's not possible, although they may not be in as widespread use these days... – twalberg Aug 23 '12 at 19:12
  • 3
    @twalberg - The question is tagged bash, so I assume that's the only shell that matters. – jordanm Aug 23 '12 at 19:41