GLOBIGNORE does not work with sudo command - bash

I'm using a mini shell script in order to 'tail' (in real time) a bunch of log files.
#!/bin/sh
oldGLOBIGNORE=$GLOBIGNORE
export GLOBIGNORE='foo-bar.log'
sudo -E tail -f -n0 /var/log/*.log
GLOBIGNORE=$oldGLOBIGNORE
As you can see, I want to log all files except the one named foo-bar.log.
the -E option of sudo should allow me to keep the GLOBIGNORE variable but it looks like it does not work.
I'm testing on Ubuntu 10.04, bash 4.1.5.
Any clue ?

Firstly — GLOBIGNORE relates to the full filepath resulting from filename-expansion, not just the last part. So you actually want to write GLOBIGNORE='/var/log/foo-bar.log'.
Secondly — you don't actually need to export GLOBIGNORE into the environment and add -E, because the /var/log/*.log gets expanded by Bash before it even invokes sudo.
Thirdly — your approach to saving the old value of GLOBIGNORE and restoring it afterward is less than ideal, because the behavior when GLOBIGNORE is unset is different from its behavior when it's set-but-empty, and your script can never restore it to being unset. Fortunately, the script doesn't need to restore it (since it's not as though a script's variables could continue to have effect after the script returns), so you can just remove that stuff.
All told, you can write:
#!/bin/sh
GLOBIGNORE=/var/log/foo-bar.log
sudo tail -f -n0 /var/log/*.log

Related

Bash script ignores positional arguments after first time used

I noticed that my script was ignoring my positional arguments in old terminal tabs, but working on recently created ones, so I decided to reduce it to the following:
TAG=test
while getopts 't:' c
do
case $c in
t)
TAG=$OPTARG
;;
esac
done
echo $TAG
And running the script I have:
~ source my_script
test
~ source my_script -t "test2"
test2
~ source my_script -t "test2"
test
I thought it could be that c was an special used variable elsewhere but after changing it to other names I had the exact same problem. I also tried adding a .sh extension to the file to see it that was a problem, but nothing worked.
Am I doing something wrong ? And why does it work the first time, but not the subsequent attempts ?
I am on MacOS and I use zsh.
Thank you very much.
The problem is that you're using source to run the script (the . command does the same thing). This makes it run in your current (interactive) shell (rather than a subprocess, like scripts normally do). This means it uses the same variables as the current shell, which is necessary if you want it to change those variables, but it can also have weird effects if you're not careful.
In this case, the problem is that getopts uses the variable OPTIND to keep track of where it is in the argument list (so it doesn't process the same argument twice). The first time you run the script with -t test2, getopts processes those arguments, and leaves OPTIND set to 3 (meaning that it's already done the first two arguments, "-t" and "test2". The second time you run it with options, it sees that OPTIND is set to 3, so it thinks it's already processed both arguments and just exits the loop.
One option is to add unset OPTIND before the while getopts loop, to reset the count and make it start from the beginning each time.
But unless there's some reason for this script to run in the current shell, it'd be better to make it a standard shell script and have it run as a subprocess. To do this:
Add a "shebang" line as the first line of the script. To make the script run in bash, that'd be either #!/bin/bash or #!/usr/bin/env bash. For zsh, use #!/bin/zsh or #!/usr/bin/env zsh. Since the script runs in a separate shell process, the you can run bash scripts from zsh or zsh scripts from bash, or whatever.
Add execute permission to the script file with chmod -x my_script (or whatever the file's actual name is).
Run the script with ./my_script (note the lack of a space between . and /), or by giving the full path to the script, or by putting the script in some directory in your PATH (the directories that're automatically searched for commands) and just running my_script. Do NOT run it with the bash, sh, zsh etc commands; these override the shebang and therefore can cause confusion.
Note: adding ".sh" to the filename is not recommended; it does nothing useful, and makes the script less convenient to run since you have to type in the extension every time you run it.
Also, a couple of recommendations: there are a bunch of all-caps variable names with special meanings (like PATH and OPTIND), so unless you want one of those special meanings, it's best to use lower- or mixed-case variable names (e.g. tag instead of TAG). Also, double-quoting variable references (e.g. echo "$tag" instead of echo $tag) avoids a lot of weird parsing headaches. Run your scripts through shellcheck.net; it's good at spotting common mistakes like this.

AppleScript : error "sh: lame: command not found" number 127

I am trying to create an AppleScript with commands below. An issue I am having is there is an error at the third line. I have no problem using the lame command in the terminal directly. In addition, lame is not a native Mac utility; I installed it on my own. Does anybody have a solution?
do shell script "cd ~/Downloads"
do shell script "say -f ~/Downloads/RE.txt -o ~/Downloads/recording.aiff"
do shell script "lame -m m ~/Downloads/recording.aiff ~/Downloads/recording.mp3"
-- error "sh: lame: command not found" number 127
do shell script "rm recording.aiff RE.txt"
To complement Paul R's helpful answer:
The thing to note is that do shell script - regrettably - does NOT see the same $PATH as shells created by Terminal.app - a notable absence is /usr/local/bin.
On my OS X 10.9.3 system, running do shell script "echo $PATH" yields merely:
/usr/bin:/bin:/usr/sbin:/sbin
There are various ways around this:
Use the full path to executables, as in Paul's solution.
Manually prepend/append /usr/local/bin, where many non-system executables live, to the $PATH - worth considering if you invoke multiple executables in a single do shell script command; e.g.:
do shell script "export PATH=\"/usr/local/bin:$PATH\"
cd ~/Downloads
say -f ~/Downloads/RE.txt -o ~/Downloads/recording.aiff
lame -m m ~/Downloads/recording.aiff ~/Downloads/recording.mp3
rm recording.aiff RE.txt"
Note how the above use a single do shell script command with multiple commands in a single string - commands can be separated by newlines or, if on the same line, with ;.
This is more efficient than multiple invocations, though adding error handling both inside the script code and around the do shell script command is advisable.
To get the same $PATH that interactive shells see (except additions made in your bash profile), you can invoke eval $(/usr/libexec/path_helper -s); as the first statement in your command string.
Other important considerations with do shell script:
bash is invoked as sh, which results in changes in behavior, most notably:
process substitution (<(...)) is not available
echo by default accepts no options and interprets escape sequences such as \n.
other, subtle changes in behavior; see http://www.gnu.org/software/bash/manual/html_node/Bash-POSIX-Mode.html
You could address these issues manually by prepending shopt -uo posix; shopt -u xpg_echo; to your command string.
The locale is set to the generic "C" locale instead of to your system's; to fix that, manually prepend export LANG='" & user locale of (system info) & ".UTF-8' to your command string.
No startup files (profiles) are read; this is not surprising, because the shell created is a noninteractive (non-login) shell, but sometimes it's handy to load one's profile by manually by prepending . ~/.bash_profile to the command string; note, however, that this makes your AppleScript less portable.
do shell script command reference: http://developer.apple.com/library/mac/#technotes/tn2065/_index.html
Probably a PATH problem - use the full path for lame, e.g.
do shell script "/usr/local/bin/lame -m m ~/Downloads/recording.aiff ~/Downloads/recording.mp3"
I have been struggling to get the path of an installed BASH command via Applescript for a long time. Using the information here, I finally succeeded.
tell me to set sox_path to (do shell script "eval $(/usr/libexec/path_helper -s); which sox")
Thanks.
Url:http://sourceforge.net/project/showfiles.php?group_id=290&package_id=309
./configure
make install

rsync run from bash script not preserving ownership

I'm trying to create a bash script which will sync a directory specified as a command line parameter to a remote server (also specified by a parameter). At the moment, I'm using eval, which solves a parameter expansion problem, but for some reason causes rsync not to preserve ownership on the remote files (apart from being Evil, I know). Running the rsync command with all the same flags and parameters from the command prompt works fine.
I tried using $() as an alternative, but I got into a real mess with variable expansion and protecting the bits that need protecting for the remote rsync path (which needs both quotes and backslashes for paths with spaces).
So - I guess 2 questions - is there a reason that eval is preventing rsync from preserving ownership (the bash script is being run as root on the source machine, and sshing to the remote machine as root too - just for now)? And is there a way of getting $() to work in this scenario? The (trimmed) code is below:
#!/bin/bash
RSYNC_CMD="/usr/bin/rsync"
RSYNC_FLAGS="-az --rsh=\"/usr/bin/ssh -i \${DST_KEY}\"" # Protect ${DST_KEY} until it is assigned later
SRC=${1} # Normally this is sense checked and processed to be a canonical path
# Logic for setting DST based on command line parameter snipped for clarity - just directly assign for testing
DST='root#some.server.com:'
DST_KEY='/path/to/sshKey.rsa'
TARG=${DST}${SRC//' '/'\ '} # Escape whitespace for target system
eval ${RSYNC_CMD} ${RSYNC_FLAGS} \"${SRC}\" \"${TARG}\" # Put quotes round the paths - even though ${TARG} is already escaped
# All synced OK - but ownership not preserved despite -a flag
I've tried changing RSYNC_CMD to sudo /usr/bin/rsync, and also adding --rsync-path="sudo /usr/bin/rsync to RSYNC_FLAGS, but neither made any difference. I just can't see what I'm missing...
The correct way to do this is to use an array. -a should already imply -o.
RSYNC_CMD="/usr/bin/rsync"
DST='root#some.server.com:'
DST_KEY='/path/to/sshKey.rsa'
RSYNC_FLAGS=(-az --rsh="/usr/bin/ssh -i ${DST_KEY}")
SRC=${1}
TARG="${DST}$SRC"
${RSYNC_CMD} "${RSYNC_FLAGS[#]}" "${SRC}" "${TARG}"
Using RSYNC_RSH instead of --rsh, you can export the variable before you set its value. This at least lets you put the export in the same area where you set the rest of the flags. Then you can defer completing its value until after you have the correct identity file.
RSYNC_CMD="/usr/bin/rsync"
export RSYNC_RSH="/usr/bin/ssh -i %s" # Use a placeholder for now; set it later
RSYNC_FLAGS=( -a -z )
# Later...
DST='root#some.server.com:'
DST_KEY='/path/to/sshKey.rsa'
RSYNC_RSH=$( printf "$RSYNC_RSH" "$DST_KEY" )
SRC=${1}
TARG="${DST}$SRC"
${RSYNC_CMD} "${RSYNC_FLAGS[#]}" "${SRC}" "${TARG}"

Is it possible to "unsource" in bash?

I have sourced a script in bash source somescript.sh. Is it possible to undo this without restarting the terminal? Alternatively, is there a way to "reset" the shell to the settings it gets upon login without restarting?
EDIT: As suggested in one of the answers, my script sets some environment variables. Is there a way to reset to the default login environment?
It is typically sufficient to simply re-exec a shell:
$ exec bash
This is not guaranteed to undo anything (sourcing the script may remove files, or execute any arbitrary command), but if your setup scripts are well written you will get a relatively clean environment. You can also try:
$ su - $(whoami)
Note that both of these solutions assume that you are talking about resetting your current shell, and not your terminal as (mis?)stated in the question. If you want to reset the terminal, try
$ reset
No. Sourcing a script executes the commands contained therein. There is no guarantee that the script doesn't do things that can't be undone (like remove files or whatever).
If the script only sets some variables and/or runs some harmless commands, then you can "undo" its action by unsetting the same variables, but even then the script might have replaced variables that already had values before with new ones, and to undo it you'd have to remember what the old values were.
If you source a script that sets some variables for your environment but you want this to be undoable, I suggest you start a new (sub)shell first and source the script in the subshell. Then to reset the environment to what it was before, just exit the subshell.
The best option seems to be to use unset to unset the environment variables that sourcing produces. Adding OLD_PATH=$PATH; export OLD_PATH to the .bashrc profile saves a backup of the login path in case one needs to revert the $PATH.
Not the most elegant solution, but this appears to do what you want:
exec $SHELL -l
My favorite approach for this would be to use a subshell within () parantheses
#!/bin/bash
(
source some_script.sh
#do something
)
# the environment before starting previous subshell should be restored here
# ...
(
source other_script.sh
#do something else
)
# the environment before starting previous subshell should be restored here
see also
https://unix.stackexchange.com/questions/138463/do-parentheses-really-put-the-command-in-a-subshell
I don't think undo of executed commands is possible in bash. You can try tset, reset for terminal initialization.
Depending what you're sourcing, you can make this script source/unsource itself.
#!/bin/bash
if [ "$IS_SOURCED" == true ] ; then
unset -f foo
export IS_SOURCED==false
else
foo () { echo bar ; }
export IS_SOURCED==true
fi

How do I prevent commands from showing up in Bash history?

Sometimes, when I run commands like rm -rf XYZ, I don't want this to be recorded in Bash history, because I might accidentally run the same command again by reverse-i-search. Is there a good way to prevent this from happening?
If you've set the HISTCONTROL environment variable to ignoreboth (which is usually set by default), commands with a leading space character will not be stored in the history (as well as duplicates).
For example:
$ HISTCONTROL=ignoreboth
$ echo test1
$ echo test2
$ history | tail -n2
1015 echo test1
1016 history | tail -n2
Here is what man bash says:
HISTCONTROL
A colon-separated list of values controlling how commands are saved on the history list. If the list of values includes ignorespace, lines which begin with a space character are not saved in the history list. A value of ignoredups causes lines matching the previous history entry to not be saved. A value of ignoreboth is shorthand for ignorespace and ignoredups. A value of erasedups causes all previous lines matching the current line to be removed from the history list before that line is saved. Any value not in the above list is ignored. If HISTCONTROL is unset, or does not include a valid value, all lines read by the shell parser are saved on the history list, subject to the value of HISTIGNORE. The second and subsequent lines of a multi-line compound command are not tested, and are added to the history regardless of the value of HISTCONTROL.
See also:
Why is bash not storing commands that start with spaces? at unix SE
Why does bash have a HISTCONTROL=ignorespace option? at unix SE
In your .bashrc/.bash_profile/wherever you want, put export HISTIGNORE=' *'. Then just begin any command you want to ignore with one space.
$ ls # goes in history
$ ls # does not
Even better use HISTIGNORE. This allows you to specify a set of patterns to be ignored (such as rm). It is better (I think) than just piping all history to /dev/null.
kill -9 $$
I know that is not as best as the previous answers, but this will kill the current Bash shell without saving anything, useful when HISTCONTROL is not set by default, you forgot to set it, or pure and simple you forgot to put a leading space and you just typed in some passwords and don't want them to remain permanently in history.
This is the quick way, and something like erasing the history file is not as good because you need to do it outside a history saving shell (log in as different user and use su/sudo, creating a background job, etc.)
You can do one of two things:
export HISTFILE=/dev/null
Or, begin the command with a space.
Or
unset HISTFILE
(similar to the previous answer only shorter: export HISTFILE=/dev/null)
I added an "Incognito" functionality to my .bashrc for when I want to run some commands without being saved without having to add spaces before each one.
Do note though that the in-memory history of the current terminal session will still be saved, but when I open a new terminal the commands issued in a past terminal's incognito session will never be seen because they were never written to the HISTFILE.
To your .bashrc:
ignoreHistory="false"
DEFAULT_HISTFILE=~/.bash_history
HISTFILE="$DEFAULT_HISTFILE"
# Toggle incognito mode
incognito() {
if [[ "$ignoreHistory" == "true" ]]; then
echo -e "\e[33mExited incognito mode\e[39m"
ignoreHistory="false"
HISTFILE="$DEFAULT_HISTFILE"
else
echo -e "\e[33mEntered incognito mode\e[39m"
ignoreHistory="true"
HISTFILE=/dev/null
fi
}
Nice little utility I think some people may find use in, you can even change the prompt to reflect whether you're in incognito mode or not.
At shell startup, I explicitly cleanup the history from the entries that I don't want to be there. For example, I don't want any rm -rf in the history (it's trauma after removing a directory full of results processed overnight, just with a single Arrow-Up + Enter :)
I put the following snippet in my init file (works with .zshrc, should also work with .bashrc)
# ...
HISTFILE=~/.zshhistory
# ...
# remove dangerous entries from the shell history
temp_histfile="/tmp/$$.temp_histfile"
grep -v -P '^rm .*-rf' $HISTFILE > $temp_histfile
mv $temp_histfile $HISTFILE

Resources