find command works differently in zsh and bash - linux

I just switched my shell from bash to zsh and I noticed some different behavior as to how the find command works.
In my old bash shell I had a function that basically replicates the behavior of the find command. For some strange reason the find command does not work in zsh but works in my old bash
Command in bash
~ /java_src: f stringBuf*
./com/sun/org/apache/xml/internal/utils/StringBufferPool.java
./java/io/StringBufferInputStream.java
./java/lang/StringBuffer.java
Same command in zsh
~ /java_src: f stringBuf*
zsh: no matches found: stringBuf*
This is the function
# find shorthand
function f() {
find . -iname "$1"
}
Any suggestions on why that might be the case?

Try quoting the argument, as in f 'stringBuf*', to avoid premature glob expansion.
If you call it unquoted, bash will do the smart thing and, after looking for the pattern in your current directory and not finding anything, will pass the parameter to the function as is.
zsh on the other hand, will try to match the pattern in your current directory, then complain about not finding anything, and not execute the function at all.
It is generally not a good idea to use unquoted wildcards (unless you mean it), since, if you have a file in your current directory called, say, "stringBuffoon", your parameter to f will be turned into "stringBuffoon", and the search will not give you the results you expect.

Related

Find command in Command substitution bash script

Hope you are doing well. I am kind of new to bash scripting so I wanted to ask you a question on something I stumbled upon just recently when I was playing around with the find command. What I had noticed was that when I search for a script name using the find command using the command substitution in the bash script, and call the variable from command substitution, it will find the script full path and also execute it right after. Can you please let me know why and how this is working?
E.g.
SEARCH=$(find / -type f -iname "script.name" 2> /dev/null)
$SEARCH
Regards
Bash performs expansions before it builds and executes the command. If you execute cd "$HOME" for example, you probably want to go to the directory stored in the variable HOME and not to a directory literally named $HOME. This allows you to put a command name inside a variable and run it by expanding the variable as the first word, although this isn't really recommended because it becomes very complex to manage for anything more than the simplest commands. The code in your question would probably not work as intended if find returned multiple results for example.
See BashParser on the BashFAQ.

Executable Unix script cannot be found by which command

I have a Unix bash script written by a former teammate that must be in my PATH, though I can't find it by manual inspection (see below). I can however execute it anywhere by just typing
$ my_script
I would like to view and edit this script. However, when I try to find it via the which command, I get a blank response. Return code indicates an error:
$ which my_script
$ echo $?
1
And yet, I can run the script. I manually combed my PATH and could not find it. Honestly, I have not encountered anything like this in 20+ years. Is there any other command besides which, and/or ways such a script could be hidden?
To determine the type of any command callable from the shell, use the type builtin.
In your case, since the presumptive script turned to be a shell function, you would have seen (assuming bash):
$ type my_script # Bash: option -t would output just 'function`
my_script is a function
my_script ()
{
... # The function's definition
From there - as you did - you can examine the profile (e.g., ~/.bash_profile) and / or initialization scripts (e.g., ~/.bashrc) to determine where the function was sourced.
Caveat: The function signature output by type is normalized to the <name> () form - even if you defined the function as function <name>
In bash you can even find out directly where a given function was defined (tip of the hat to this superuser.com answer and Charles Duffy for inspiring me to find it and coming up with the most succinct form):
$ (shopt -s extdebug; declare -F my_func)
my_func 112 /Users/jdoe/.bashrc # sample output
112 is the line number inside the script indicated.
Found it. I am leaving the question and answer in case someone else runs into a similar situation. I am marking a different answer as the final answer as it contains some more useful tools for finding what I found (see below).
Summary
The "script" was actually a bash function that had been written into a sourced . file.
Details
The account's .bashrc file was sourcing another . file:
. ~/.other-dot-file
In that other . file was the bash function called my_script.
function my_script {
....
}
Since this was sourced, the function could be executed from the command line, mimicking the behavior of a full-fledged script. There is no other code that calls this function - it is purely meant to be called from the command line, as if it were a script.
This willful obscurity is part of the reason the author is no longer with us.
Try
1. type my_script
or
2. find / -name my_script -print
Note that find will throw a lot of permission errors if you run this as a normal user. You can run it as root to avoid that or pipe STDERR to /dev/null

File execution with dot space versus dot slash

I am attempting to work with an existing library of code but have encountered an issue. In short, I execute a shell script (let's call this one A) whose first act is to call another script (B). Script B is in my current directory (a requirement of the program I'm using). The software's manual makes reference to bash, however comments in A suggest it was developed in ksh. I've been operating in bash so far.
Inside A, the line to execute B is simply:
. B
It uses the "dot space" syntax to call the program. It doesn't do anything unusual like sudo.
When I call A without dot space syntax, i.e.:
./A
it always errors saying it cannot find the file B. I added pwd, ls, whoami, echo $SHELL, and echo $PATH lines to A to debug and confirmed that B is in fact right there, the script is running with the same $SHELL as I am at the command prompt, the script is the same user as I am, and the script has the same search path $PATH as I do. I also verified if I do:
. B
at the command line, it works just fine. But, if I change the syntax inside A to:
./B
instead, then A executes successfully.
Similarly, if I execute A with dot space syntax, then both . B and ./B work.
Summarizing:
./A only works if A contains ./B syntax.
. A works for A with either ./B or . B syntax.
I understand that using dot space (i.e. . A) syntax executes without forking to a subshell, but I don't see how this could result in the behavior I'm observing given that the file is clearly right there. Is there something I'm missing about the nuances of syntax or parent/child process workspaces? Magic?
UPDATE1: Added info indicating that the script may have been developed in ksh, while I'm using bash.
UPDATE2: Added checking to verify $PATH is the same.
UPDATE3: The script says it was written for ksh, but it is running in bash. In response to Kenster's answer, I found that running bash -posix then . B fails at the command line. That indicates that the difference in environments between the command line and the script is that the latter is running bash in a POSIX-compliant mode, whereas the command line is not. Looking a little closer, I see this in the bash man page:
When invoked as sh, bash enters posix mode after the startup files are read.
The shebang for A is indeed #!/bin/sh.
In summary, when I run A without dot space syntax, it's forking to its own subshell, which is in POSIX-compliant mode because the shebang is #!/bin/sh (instead of, e.g., #!/bin/bash. This is the critical difference between the command line and script runtime environments that leads to A being unable to find B.
Let's start with how the command path works and when it's used. When you run a command like:
ls /tmp
The ls here doesn't contain a / character, so the shell searches the directories in your command path (the value of the PATH environment variable) for a file named ls. If it finds one, it executes that file. In the case of ls, it's usually in /bin or /usr/bin, and both of those directories are typically in your path.
When you issue a command with a / in the command word:
/bin/ls /tmp
The shell doesn't search the command path. It looks specifically for the file /bin/ls and executes that.
Running ./A is an example of running a command with a / in its name. The shell doesn't search the command path; it looks specifically for the file named ./A and executes that. "." is shorthand for your current working directory, so ./A refers to a file that ought to be in your current working directory. If the file exists, it's run like any other command. For example:
cd /bin
./ls
would work to run /bin/ls.
Running . A is an example of sourcing a file. The file being sourced must be a text file containing shell commands. It is executed by the current shell, without starting a new process. The file to be sourced is found in the same way that commands are found. If the name of the file contains a /, then the shell reads the specific file that you named. If the name of the file doesn't contain a /, then the shell looks for it in the command path.
. A # Looks for A using the command path, so might source /bin/A for example
. ./A # Specifically sources ./A
So, your script tries to execute . B and fails claiming that B doesn't exist, even though there's a file named B right there in your current directory. As discussed above, the shell would have searched your command path for B because B didn't contain any / characters. When searching for a command, the shell doesn't automatically search the current directory. It only searches the current directory if that directory is part of the command path.
In short, . B is probably failing because you don't have "." (current directory) in your command path, and the script which is trying to source B is assuming that "." is part of your path. In my opinion, this is a bug in the script. Lots of people run without "." in their path, and the script shouldn't depend on that.
Edit:
You say the script uses ksh, while you are using bash. Ksh follows the POSIX standard--actually, KSH was the basis for the POSIX standard--and always searches the command path as I described. Bash has a flag called "POSIX mode" which controls how strictly it follows the POSIX standard. When not in POSIX mode--which is how people generally use it--bash will check the current directory for the file to be sourced if it doesn't find the file in the command path.
If you were to run bash -posix and run . B within that bash instance, you should find that it won't work.

How do I set tilde expansion in zsh?

I am trying to achieve expansion of the tilde into the $HOME variable with zsh, and was wondering what best practice is in this case?
Basically what I want is that:
$ ls ~
on pressing TAB expands to
$ ls /home/myusername
where HOME=/home/myusername. The _tilde function usually does a ldap search, followed by named dirs, followed by the dir stack, none of which I need.
What is the best way to achieve this?

Vim: `cd` to path stored in variable

I'm pretty new to vim, and I'm having a hard time understanding some subtleties with vim scripting. Specifically, I'm having trouble working with commands that expect an unquoted-string (is there a name for this?). For example
cd some/unquoted/string/path
The problem is that I'd like to pass a variable, but calling
let pathname = 'some/path'
cd pathname
will try to change the current directory to 'pathname' instead of 'some/path'. One way around this is to use
let cmd = 'cd ' . pathname
execute cmd
but this seems a bit roundabout. This StackOverflow question actually uses cd with a variable, but it doesn't work on my system ("a:path" is treated as the path as described above).
I'm using cd as a specific example, but this behavior isn't unique to cd; for example, the edit command also behaves this way. (Is there a name for this type of command?)
TL;DR: use execute 'cd' fnameescape(pathname)
Explanation: Lots of basic commands that take filenames as an argument support backtick syntax:
command `shell command`
or
command `=vim_expression`
so your example may be written as
cd `=pathname`
if you are running this in a controlled environment. You must not use this variant in plugins because a) there is &wildignore setting that may step in your way: set wildignore=*|cd =pathname will make cd fail regardless of what is stored in the pathname and b) if pathname contains newlines it will be split into two or more directories. Thus what you should use for any piece of code you intend to share is
execute 'cd' fnameescape(pathname)
Note that you must not use execute "cd" pathname because it does not care about special characters in pathname (for example, space).
The basic commands in Vim never do any processing of variables (how would it know that you didn't mean to change to the pathname directory instead of the some/path one?). You don't have to be quite as roundabout as you suggested, you can just do:
exe 'cd' pathname
Note that exe concatenates arguments with a space automatically, so you don't have to do:
exe 'cd ' . pathname
A lot time ago I wrote this plugin (function FixPathName() in order to solve this kind of issues. Now vim has some new functions like shellescape() when the path need to be used with external commands.

Resources