According to section 3.7.1 of the Bash manual, variable assignments at the beginning of a command line should be visible to the invoked program.
e.g.
DIR=/tmp ls $DIR
should behave as if I've typed "ls /tmp" - and the variable DIR should not persist after executing the command.
Cygwin Bash (GNU bash, version 3.2.51(24)-release (i686-pc-cygwin)) appears not to do this - the above command behaves as if $DIR is not defined. This is confirmed by other tests such as "DIR=/tmp echo $DIR", "DIR=/tmp set" etc.
Note that adding a semicolon works ("DIR=/tmp ; ls $DIR"), but leaves the variable defined after the command.
Why is this not working as expected?
It does work - but not in the context that you are trying to make it work.
DIR=/tmp ls $DIR
The environment variable DIR is set for ls - but is not set when the shell expands the $DIR of the command. This is the way the Bourne shell behaved; it is the way its successors such as the Korn shell and Bash behave.
You could see that DIR is set by changing ls $DIR to env; that would show the environment of an external (not built-in) command.
In this example, think about it for a moment: what you've typed is 9 extra characters compared with:
ls /tmp
If you must have it set and removed, then this does the trick:
(DIR=/tmp; ls $DIR)
The variable is set before the shell evaluates ls $DIR, but the whole command is run in a sub-shell so it has no impact on the invoking shell.
Related
I know I can run an "original" command (not alias) using either \ or "":
\ls
"ls"
This doesn't work for functions though. Also it requires me to use that syntax every time.
Is it possible in a sourced script to disable all functions/aliases from the parent process (one which runs my script)? I.e. if a user in their terminal has some aliases functions defined I want them disabled in my script (but of course I still want to be able to define and use aliases/functions of my own).
Types of Commands in Bash
Bash knows different types of commands which can shadow each other. The precedence of these types is:
aliases
can be defined by the user using alias cmd=...
functions
can be defined by the user using cmd() { ... }
built-ins
are directly implement in bash and cannot be altered. help and enable list all built-ins.
Executable files in $PATH
Meaning if you type cmd arg1 arg2 ... you use the alias cmd if it is defined, otherwise you use the function cmd if it is defined, otherwise you use the built-in cmd if it is built-in, otherwise you use the first executable cmd from the directories in $PATH if there is one, otherwise you end up with the error -bash: cmd command not found.
Which of these cases applies for cmd can be checked using type -a cmd.
Manual Precedence Control
Bash allows you to influence which type to pick using quoting and the built-ins command and builtin.
\cmd
suppresses aliases
uses functions, built-ins, executables
command cmd
suppresses aliases and functions
uses built-ins and executables
builtin cmd
supresses aliases, functions, and executables
uses only built-ins
enable -n cmd
disables the built-in cmd completely, such that afterwards only
aliases, functions, and executables are used
env cmd
not a bash built-in, therefore it doesn't really suppress anything but
uses only executables
Examples
Shadowing is perfectly normal. For instance, bash has its own built-in echo, but your system also has /bin/echo. Both implementations may differ. For instance, my echo from bash 5 supports \uXXXX but my echo from GNU coreutils 8.3 does not. The possibility of such differences becomes even more clear if you add your own implementations using aliases and functions. Here's an example in an interactive bash session ($ is the prompt):
$ echo() { printf "function echo: %s\n" "$*"; }
$ alias echo='printf "alias echo: %s %s %s\n"'
$ type -a echo
echo is aliased to `printf "alias echo: %s %s %s\n"'
echo is a function
echo ()
{
printf "function echo: %s\n" "$*"
}
echo is a shell builtin
echo is /bin/echo
$ echo -e '\u2261'
alias echo: -e \u2261
$ \echo -e '\u2261'
function echo: -e \u2261
# use the built-in (or executable file if there was no such built-in)
$ command echo -e '\u2261'
≡
$ builtin echo -e '\u2261'
≡
# use the executable /bin/echo
$ env echo -e '\u2261'
\u2261
$ enable -n echo
# use the executable /bin/echo (`command` is needed to skip the alias and function)
$ command echo -e '\u2261'
\u2261
Answering your Question
Unfortunately I'm not aware of something like enable to permanently disable alias and function lookup. You could try some hacks like backing up all aliases and functions, doing unset -f and unalias on them, and restoring them at the end. However, unset may fail for readonly functions. The better way would be to use bash -c '... functions and aliases have no effect here ...' for the parts where you don't really need the benefits of source. For the other parts, prefix everything with command.
Please note: The caller who sources your script may even disable or shadow command, builtin, and so on -- therefore you can never be sure that you are actually using the commands you expected. Even writing /usr/bin/env executable or /path/to/the/executable does not help as a function can have the name and $PATH or the file system can be altered.
However, that shouldn't be your concern. The one who sources your script should be responsible for providing the correct environment.
Edit: this answer might no longer be relevant since you edited the question to clarify that the script is being sourced, not being executed in a subshell.
This happens by default. Proof:
$ function x() { echo 'hi'; }
$ x
hi
$ bash
# We are now in a subshell.
$ x
bash: x: command not found
Functions are often defined in one of the shell's startup files: .bashrc, .profile or .bash_profile. Which of these are sourced depends on whether the shell is a login shell and/or an interactive shell. A shell that invoked to execute a shell script is neither a login shell nor an interactive shell, and in this case none of those files are sourced.
EDIT: I should read more carefully, as you don't want to source a script, but be sourced, the following is for the other way around:
Functions
If you source your parent script at the beginning, you can just loop through the defined functions and unset them.
declare -F will list all defined functions but in the format declare -f functioname, so you have to get only the name:
IFS=$'\n'
for f in $(declare -F|cut -d ' ' -f 3); do
unset -f $f
done
Aliases
Alias should not be sourced in as i remember, but if they are there you can do
unalias -a
to unset them all.
I have an rsync command in my csh script like this:
#! /bin/csh -f
set source_dir = "blahDir/blahBlahDir"
set dest_dir = "foo/anotherFoo"
rsync -av --exclude=*.csv ${source_dir} ${dest_dir}
When I run this I get the following error:
rsync: No match.
If I remove the --exclude option it works. I wrote the equivalent script in bash and that works as expected
#/bin/bash -f
source_dir="blahDir/blahBlahDir"
dest_dir="foo/anotherFoo"
rsync -av --exclude=*.csv ${source_dir} ${dest_dir}
The problem is that this has to be done in csh only. Any ideas on how I can get his to work?
It's because csh is trying to expand --exclude=*.csv into a filename, and complaining because it cannot find a file matching that pattern.
You can get around this by enclosing the option in quotes:
rsynv -rv '--exclude=*.csv' ...
or escaping the asterisk:
rsynv -rv --exclude=\*.csv ...
This is a consequence of the way csh and bash differ in their default treatment of arguments with wildcards that don't match a file. csh will complain while bash will simply leave it alone.
You may think bash has chosen the better way but that's not necessarily so, as shown in the following transcript where you have a file matching the argument:
pax> touch -- '--file=xyzzy.csv' ; ls -- *.csv
--file=xyzzy.csv
pax> echo --file=*.csv
--file=xyzzy.csv
You can see there that the bash shell expands the argument rather than giving it to the program as is. Both sides have their pros and cons.
[root#MGWSDT_FEWS bin]# type cd
cd is a shell builtin
[root#MGWSDT_FEWS bin]# which cd
/usr/bin/which: no cd in (.:/usr/expect/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/opt/real/RealPlayer:/root/bin)
It says that because cd is a builtin command, so 'which cd' failed.
'pwd' is also a builtin command, why 'which pwd' can get its location?
[root#MGWSDT_FEWS ~]# type pwd
pwd is a shell builtin
[root#MGWSDT_FEWS ~]# which pwd
/bin/pwd
Understand that for sudo or any other command that is not a shell builtin, when the command terminates, the shell picks up where it left off before the command was run (of course, the shell's current working directory won't change). As such, cd must be a shell builtin; otherwise, when cd terminates, the shell is back to the same old current working directory. In other words, a child process (i.e. an external command) can't change the working directory of the parent process (i.e. the shell).
Courtesy: https://bbs.archlinux.org/viewtopic.php?id=127225
cd command is bash's command. (bash built in command)
You can find cd's usage in "man bash & find cd"
Contrary, pwd is standalone command.
Builtin commands are internal commands in the shell and has a higher priority over external commands (files). And builtin commands also have lower priority than functions so builtin is actually helpful if you have a function with the same name as the builtin command like this:
function cd {
if [[ $# -gt 0 ]]; then
echo "Changing directory to $1."
builtin cd "$1"
else
echo "Changing to default directory."
builtin cd
fi
}
cd "/some/where"
As a summary functions gets called first before builtins, and builtins gets called first before binary commands or files. The type command I think would also follow that order when interpreting an argument.
There are several instances where "commands" are duplicated, but of course this will depend on your installation. If you have a shell built-in called pwd then that will be used, but there might be shells (csh) that do not have pwd as a built-in.
printf is a shell built-in for bash and ksh93 but not for ksh88 or csh.
Another (weirder) example is [, which is a shell built-in but (on some systems) a symbolic link to test (which might also be a shell built-in).
Your installation is there to support many shells and other programs, not just bash.
cd has to be shell-built-in, because it were a separate process, then the effect of the command would have vanished, after the command exits. (See Sakthi Kumar's answer.)
On the other hand, some other commands like pwd, test are available as binaries, like /bin/pwd, /usr/bin/test... However, for optimization, shell has also implementation of these binaries within /bin/bash itself, so that the overhead of creating a separate process is avoided.
when you call pwd, shell calls its own pwd implementation. You can call the binary /bin/pwd using command pwd instead of just pwd.
Commands such as pwd and echo are bash-builtins and also available as external commands. You can use enable to enable/disable shell builtins.
The following example iillustrates how pwd can be invoked either as a shell-builtin or as external command i.e. /bin/pwd.
$ type pwd
pwd is a shell builtin
$ which pwd
/bin/pwd
$ enable -n pwd
$ type pwd
pwd is /bin/pwd
$ which pwd
/bin/pwd
$ enable pwd
$ type pwd
pwd is a shell builtin
$ which pwd
/bin/pwd
The following shell script changes current the directory to the desktop.
v=~/Desktop/
cd $v
pwd # desktop
The following script changes the current directory to home directory instead of generating error.
cd $undefined_variable
pwd # home directory
echo $? # 0
I'm afraid that the script will remove important files if I misspelled a variable for new current directory.
Generally, how do you safely change current directory with variable in shell script?
Use:
cd ${variable:?}
if $variable is not defined or empty then bash will throw an error and exit. It's like the set -u option but not global through the file.
You can set -u to make bash exit with an error each time you expand an undefined variable.
You could use the test -d condition (checks whether the specified variable is a directory), i.e.
if [[ -d $undefined_variable ]]
then
cd $undefined_variable
echo "This will not be printed if $undefined_variable is not defined"
fi
See also here for further test options...
The Bourne Shells have a construct to substitute a value for undefined variables, ${varname-subtitution}. You can use this to have a safe fallback directory in case the variable is undefined:
cd "${undefined-/tmp/backupdir}"
If there is a variable named undefined, its value is substituted, otherwise /tmp/backupdir is substituted.
Note that I also put the variable expansion in double quotes. This is used to prevent word splitting on strings containing spaces (very common for Windows directories). This way it works even for directories with spaces.
For the gory details on all the shell substitution constructs (there are seven more for POSIX shells), read your shell manual's Parameter Substitution section.
You have to write a wrapper (this work in bash):
cd() {
if [ $# -ne 1 ] ;then
echo "cd need exactly 1 argument" >&2
return 2
fi
builtin cd "$1"
}
yes, that's shell
if you type cd without parameter it will jump to home dir.
You can can check the variable of null or empty before you cd command.
check like (cd only be called if targetDir is not empty):
test -z "$targetDir" || cd $targetDir
check like (cd only be called if targetDir really exist):
test -d "$targetDir" && cd $targetDir
Note: Thanks for -1, should read the last sentence too. So I added the real answer.
In bash i get the executing script's parent folder name by this line
SCRIPT_PARENT=`readlink -f ${BASH_SOURCE%/*}/..`
Is there any way to achieve this in zsh in a way that works both in zsh and bash?
Assume i have got a file /some/folder/rootfolder/subfolder/script with the contents:
echo `magic-i-am-looking-for`
I want it to behave this way:
$ cd /some/other/folder
$ . /some/folder/rootfolder/subfolder/script
/some/folder/rootfolder
$ . ../../folder/rootfolder/subfolder/script
/some/folder/rootfolder
$ cd /some/folder/rootfolder
$ . subfolder/script
/some/folder/rootfolder
$ cd subfolder
$ . script
/some/folder/rootfolder
This should work in bash and zsh. My first implements this behavior, but does due to $BASH_SOURCE not work in zsh.
So basically its:
Is there a way to emulate $BASH_SOURCE in zsh, that also works in bash?
I now realized that $0 in zsh behaves like $BASH_SOURCE in bash. So using $BASH_SOURCE when available and falling back to $0 solves my problem:
${BASH_SOURCE:-$0}
There is a little zsh edge case left, when sourcing from $PATH like:
zsh> cat ../script
echo \$0: $0
echo \$BASH_SOURCE: $BASH_SOURCE
echo '${BASH_SOURCE:-$0}:' ${BASH_SOURCE:-$0}
zsh> . script
$0: script
$BASH_SOURCE:
${BASH_SOURCE:-$0}: script
bash> . script
$0: bash
$BASH_SOURCE: /home/me/script
${BASH_SOURCE:-$0}: /home/me/script
I could do a which script but this would not play nice with other cases
While it would be easy to do this in zsh, it is just as easy to use pure bash which is able to be evaluated in zsh. If you cannot use any command that may or may not be on your path, then you can only use variable alteration to achieve what you want:
SCRIPT_SOURCE=${0%/*}
This is likely to be a relative path. If you really want the full path then you will have to resort to an external command (you could implement it yourself, but it would be a lot of work to avoid using a very available command):
SCRIPT_SOURCE=$(/bin/readlink -f ${0%/*})
This doesn't depend on your $PATH, it just depends on /bin/readlink being present. Which it almost certainly is.
Now, you wanted this to be a sourced file. This is fine, as you can just export any variable you set, however if you execute the above then $0 will be the location of the sourced file and not the location of the calling script.
This just means you need to set a variable to hold the $0 value which the sourced script knows about. For example:
The script you will source:
echo ${LOCATION%/*}
The script that sources that script:
LOCATION=$0
<source script here>
But given that the ${0%/*} expansion is so compact, you could just use that in place of the script.
Because you were able to run the command from your $PATH I'll do something like that:
SCRIPT_PARENT=$(readlink -f "$(which $0)/..")
Is that your desired output?