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.
Related
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
I'm pretty new to Bash and Linux in general. I've created a couple scripts that I would like to be able to use by typing the command, rather than the directory and the executable file. I'm using Debian Jessie if that makes a difference.
The path to one of my scripts is ~/Scripts/DIR_1/My_Script.sh while another is in ~Scripts/DIR_2/My_Other_Script.sh. I would like ALL of the scripts contained withing the Scripts directory to be indexed as commands regardless of directory/path depth.
I've appended this text to the end of my .bashrc file...
PATH=${PATH}:$(find ~/Scripts -type d | sed '/\/\\./d' | tr '\n' ':' | sed 's/:$//')
Since I'm pretty new to this kind of thing, I had to steal that line from here.
When I try to run My_Script from the command line withing a sub directory of my home folder (or anywhere else for that matter) I get My_Script: command not found
I will readily admit that I might have misunderstood the process of adding a bash script to the command line.
How do I recursively add bash scripts as commands? What is wrong with the process I'm currently using?
I think your issue is that you're not putting the .sh, that is part of your file name.
Normally, pressing tab after having typed only the first letter should complete the command up to the point where there is an ambiguity (or completely if there's none). In case of ambiguity, pressing tab a second time shows the options. So in your case, if you type My<tab><tab> you should have options My_Script.sh and My_Other_Script.sh displayed. And if you type My_Script<tab> it should complete by putting My_Script.sh
Edit
I forgot to precise that you can check the value of PATH by doing echo $PATH. This will allow you to check that the command you copied did what you wanted.
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.
When I type echo $0 I see -
I expect to see bash or some filename, what does it mean if I just get a "-"?
A hyphen in front of $0 means that this program is a login shell.
note: $0 does not always contain accurate path to the running executable as there is a way to override it when calling execve(2).
I get '-bash', a few weeks ago, I played with modifying a process name visible when you run ps or top/htop or echo $0. To answer you question directly, I don't think it means anything. Echo is a built-in function of bash, so when it checks the arguments list, bash is actually doing the checking, and seeing itself there.
Your intuition is correct, if you wrote echo $0 in a script file, and ran that, you would see the script's filename.
So based on one of your comments, you're really want to know how to determine what shell you're running; you assumed $0 was the solution, and asked about that, but as you've seen $0 won't reliably tell you what you need to know.
If you're running bash, then several unexported variables will be set, including $BASH_VERSION. If you're running tcsh, then the shell variables $tcsh and $version will be set. (Note that $version is an excessively generic name; I've run into problems where some system-wide startup script sets it and clobbers the tcsh-specific variable. But $tcsh should be reliable.)
The real problem, though, is that bash and tcsh syntax are mostly incompatible. It might be possible to write a script that can execute when invoked (via . or source) from either tcsh or bash, but it would be difficult and ugly.
The usual approach is to have separate setup files, one for each shell you use. For example, if you're running bash you might run
. ~/setup.bash
or
. ~/setup.sh
and if you're running tcsh you might run
source ~/setup.tcsh
or
source ~/setup.csh
The .sh or .csh versions refer to the ancestors of both shells; it makes sense to use those suffixes if you're not using any bash-specific or tcsh-specific features.
But that requires knowing which shell you're running.
You could probably set up an alias in your .cshrc, .tcshrc, or.login, and an alias or function in your.profile,.bash_profile, or.bashrc` that will invoke whichever script you need.
Or if you want to do the setup every time you login, or every time you start a new interactive shell, you can put the commands directly in the appropriate shell startup file(s). Of course the commands will be different for tcsh vs. bash.
I am trying to create a script that will run a program on each file in a list. I have been trying to do this using a .csh file (I have no clue if this is the best way), and I started with something as simple as hello world
echo "hello world"
The problem is that I cannot execute this script, or verify that it works correctly. (I was trying to do ./testscript.csh which is obviously wrong). I haven't been able to find anything that really explains how to run C Scripts, and I'm guessing there's a better way to do this too. What do I need to change to get this to work?
You need to mark it as executable; Unix doesn't execute things arbitrarily based on extension.
chmod +x testscript.csh
Also, I strongly recommend using sh or bash instead of csh, or you will soon learn about the idiosyncrasies of csh's looping and control flow constructs (some things only work inside them if done a particular way, in particular with the single-line versions things are very limited).
You can use ./testscript.csh. You will however need to make it executable first:
chmod u+x testscript.csh
Which means set testscript to have execute permissions for the user (who ever the file is owned by - which in this case should be yourself!)
Also to tell the OS that this is a csh script you will need put
#! /path/to/csh
on the first line (where /path/to/csh is the full path to csh on your system. You can find that out by issuing the command which csh).
That should give you the behvaiour you want.
EDIT As discussed in some of the comments, you may want to choose an alternative shell to C Shell (csh). It is not the friendliest one for scripting.
You have several options.
You can run the script from within your current shell. If you're running csh or tcsh, the syntax is source testscript.csh. If you're running sh, bash, ksh, etc., the syntax is . ./testscript.sh. Note that I've changed the file name suffix; source or . runs the commands in the named file in your current shell. If you have any shell-specific syntax, this won't work unless your interactive shell matches the one used by the script. If the script is very simple (just a sequence of simple commands), that might not matter.
You can make the script an executable program. (I'm going to repeat some of what others have already written.) Add a "shebang" as the first line. For a csh script, use #!/bin/csh -f. The -f avoids running commands in your own personal startup scripts (.cshrc et al), which saves time and makes it more likely that others will be able to use it. Or, for a sh script (recommended), used #!/bin/sh (no -f, it has a completely different meaning). In either case, run chmod +x the_script, then ./the_script.
There's a trick I often use when I want to perform some moderately complex action. Say I want to delete some, but not all, files in the current directory, but the criterion can't be expressed conveniently in a single command. I might run ls > tmp.sh, then edit tmp.h with my favorite editor (mine happens to be vim). Then I go through the list of files and delete all the ones that I want to leave alone. Once I've done that, I can replace each file name with a command to remove it; in vim, :%s/.*/rm -f &/. I add a #!/bin/sh at the top save it, chmod +x foo.sh, then ./foo.sh. (If some of the file names might have special characters, I can use :%s/.*/rm -f '&'/.)