History Command Works in a Terminal, But Doesn't When Written as a Bash Script

History command works in a terminal, but doesn't when written as a bash script

Looking at your history only makes sense in an interactive shell. Make that command a function instead of a standalone script. In your ~/.bashrc, put

popular_history() {
history | sort -k2 | uniq -c --skip-fields=1 | sort -r -g | head
}

Why history command is default disabled for bash scripts


In General

  • For an overwhelming majority of scripts, reading dotfiles for history would be pure startup-time overhead, with that content never used at runtime. This would make all shell scripts take longer to start up, with no compensating benefit.
  • If noninteractive scripts did support history expansion, this would make their behavior dependent on prior interactive actions, thus harder to predict and different between invocations. This is, in particular, a compelling reason not to have set -H on by default in noninteractive script invocation.
  • History is specified in the User Portability Utilities section of the POSIX sh standard, which is focused around support for interactive scripting. Quoting from the standard, with emphasis added:

    When the sh utility is being used interactively, it shall maintain a list of commands previously entered from the terminal in the file named by the HISTFILE environment variable. The type, size, and internal format of this file are unspecified. Multiple sh processes can share access to the file for a user, if file access permissions allow this; see the description of the HISTFILE environment variable.


Finally, some context:

history -c is needed in an interactive script because that interactive shell will be writing a new copy of HISTFILE when it exits; if you didn't use a special command, it would potentially write back content that you instead want to clear. In a noninteractive shell, it won't be writing any history anyhow, so you might as well simply delete or truncate the history file.

history -c doesn't work when called inside a script?

Don't try to clear history -- even though that's the most obvious way that passing a password on the command line exposes it, that action is giving a false sense of security: Passwords given on the command line are trivial to capture via other processes running on the same machine (even under untrusted accounts!) even without history involved at all.

Moreover, as you note, a shell can only modify its own in-memory state, not the in-memory state of the separate process that started it (which may not even be the same shell, or a shell at all!).


Instead, modify your Python program's calling convention to read the password direct from the TTY (as SSH does), or from the environment. For the latter, usage might look like:

# assumes you renamed aesencrypt.py to aesencrypt, ran chmod +x, and gave a valid shebang
password="somePassword" aesencrypt outFile

...and you would want to modify your Python script to do something like:

#!/usr/bin/env python
import os, sys

filename = sys.argv[1]
password = os.environ['password']

# ...put the rest of your logic here.

When using the terminal history, can I force confirmation when executing dangerous commands from my history?


Use Functions as Command Wrappers

The shell doesn't directly support this feature, in part because a command is stored in history before it is executed. However, you can largely fake it by wrapping your "dangerous" commands in functions that take precedence over the real commands. For example:

rm () {
local regex='[Yy]'
if history | sed '$d' | pcregrep -q "^\s+\d+\s+${FUNCNAME} $*"; then
read -p 'Are you sure? '
[[ "$REPLY" =~ $regex ]] || return 1
fi
command "$FUNCNAME" "$@"
}

The secret sauce is the sed command that strips out the current command that's just been stored in history. Without that, grep would always find the command in the history.

Caveats

  1. Note the requirement for a grep compiled with PCRE support. You will need to adjust the regular expression if you don't have pcregrep or egrep compiled with PCRE support.
  2. You might also have to adjust quoting or use eval if word splitting doesn't happen the way you think it should, but it worked fine for me in casual testing. Your mileage may vary.

Bash one liner works but script does not

The line echo "$(( i++))) $item" has one closing parentheses in excess.

echo "$(( i++ )) $item"

If you try to use history in a script, it will fail.

Try running this script:

#!/bin/bash
history

It will print nothing because there is no history stored (for this instance of the shell). To read history you need to provide the file with the stored history, call the builtin history to read -r and finally you can list the history from memory:

#!/bin/bash
HISTFILE="$HOME/.bash_history"
history -r
history

That doesn't mean that commands will be written to the file, that's controlled by a different option.

#!/bin/bash

read -p 'Search history for? ' string

i=0
OLDIFS=$IFS
IFS=$'\n'

HISTFILE="$HOME/.bash_history"
history -r
IFS=$'\n' read -d '' -a arr <<<"$(history | grep "$string" | cut -c8-)"

for item in ${arr[@]}
do echo "$(( i++ )) $item"
done

Diffrence between bash script.sh and ./script.sh

History is disabled by BASH in non-interactive shells by-default. If you want to enable it however, you can do so like this:

#!/bin/bash

echo $HISTFILE # will be empty in non-iteractive shell

HISTFILE=~/.bash_history # set it again
set -o history
# the command will work now
history

The reason this is done is to avoid cluttering the history by any commands being run by any shell scripts.

Adding hashbang (meaning the file is to be interpreted as a script by the program specified in your hashbang) to your script when being run via ./env.sh invokes your script using the binary /bin/bash i.e. run via bash, thus again printing no history.



Related Topics



Leave a reply



Submit