Is it possible to get command arguments now executing? - linux

I want to declare aliases which append argument if no argument specified.
For example,
alias vimtutor=‘vimtutor $([ $# -eq 0 ] && echo ja.utf-8)’
I expected executing vimtutor with no argument is replaced to vimtutor ja.utf-8, but it doesn’t work properly.
So, I tried some tests on bash shell.
$ echo $#
0
$ echo a b c $#
a b c 0
$ echo $0
-bash
And I understood it is impossible to get command arguments now executing in normal way.
Now, I declare shell function showing below,
vimtutor(){ /usr/bin/vimtutor $([ $# -eq 0 ] && echo ja.utf-8) “$#“; }
It woks as expected but feel not smart way.
Is there any idea to solve this problem?

The variables $#, $0, etc when used in an alias references your bash session variables; the arguments used when your bash session was started, not the arguments passed to the alias command.
I suggest you put your command in a script, and alias to that script. I.e.
$ echo 'vimtutor $([ $# -eq 0 ] && echo ja.utf-8)' > ~/vimtutor.sh
$ chmod +x ~/vimtutor.sh
$ alias vimtutor='~/vimtutor.sh'

Related

Passing values to a specific command in a chained command using an alias [duplicate]

I used to use CShell (csh), which lets you make an alias that takes a parameter. The notation was something like
alias junk="mv \\!* ~/.Trash"
In Bash, this does not seem to work. Given that Bash has a multitude of useful features, I would assume that this one has been implemented but I am wondering how.
Bash alias does not directly accept parameters. You will have to create a function.
alias does not accept parameters but a function can be called just like an alias. For example:
myfunction() {
#do things with parameters like $1 such as
mv "$1" "$1.bak"
cp "$2" "$1"
}
myfunction old.conf new.conf #calls `myfunction`
By the way, Bash functions defined in your .bashrc and other files are available as commands within your shell. So for instance you can call the earlier function like this
$ myfunction original.conf my.conf
Refining the answer above, you can get 1-line syntax like you can for aliases, which is more convenient for ad-hoc definitions in a shell or .bashrc files:
bash$ myfunction() { mv "$1" "$1.bak" && cp -i "$2" "$1"; }
bash$ myfunction original.conf my.conf
Don't forget the semi-colon before the closing right-bracket. Similarly, for the actual question:
csh% alias junk="mv \\!* ~/.Trash"
bash$ junk() { mv "$#" ~/.Trash/; }
Or:
bash$ junk() { for item in "$#" ; do echo "Trashing: $item" ; mv "$item" ~/.Trash/; done; }
The question is simply asked wrong. You don't make an alias that takes parameters because alias just adds a second name for something that already exists. The functionality the OP wants is the function command to create a new function. You do not need to alias the function as the function already has a name.
I think you want something like this :
function trash() { mv "$#" ~/.Trash; }
That's it! You can use parameters $1, $2, $3, etc, or just stuff them all with $#
TL;DR: Do this instead
Its far easier and more readable to use a function than an alias to put arguments in the middle of a command.
$ wrap_args() { echo "before $# after"; }
$ wrap_args 1 2 3
before 1 2 3 after
If you read on, you'll learn things that you don't need to know about shell argument processing. Knowledge is dangerous. Just get the outcome you want, before the dark side forever controls your destiny.
Clarification
bash aliases do accept arguments, but only at the end:
$ alias speak=echo
$ speak hello world
hello world
Putting arguments into the middle of command via alias is indeed possible but it gets ugly.
Don't try this at home, kiddies!
If you like circumventing limitations and doing what others say is impossible, here's the recipe. Just don't blame me if your hair gets frazzled and your face ends up covered in soot mad-scientist-style.
The workaround is to pass the arguments that alias accepts only at the end to a wrapper that will insert them in the middle and then execute your command.
Solution 1
If you're really against using a function per se, you can use:
$ alias wrap_args='f(){ echo before "$#" after; unset -f f; }; f'
$ wrap_args x y z
before x y z after
You can replace $# with $1 if you only want the first argument.
Explanation 1
This creates a temporary function f, which is passed the arguments (note that f is called at the very end). The unset -f removes the function definition as the alias is executed so it doesn't hang around afterwards.
Solution 2
You can also use a subshell:
$ alias wrap_args='sh -c '\''echo before "$#" after'\'' _'
Explanation 2
The alias builds a command like:
sh -c 'echo before "$#" after' _
Comments:
The placeholder _ is required, but it could be anything. It gets set to sh's $0, and is required so that the first of the user-given arguments don't get consumed. Demonstration:
sh -c 'echo Consumed: "$0" Printing: "$#"' alcohol drunken babble
Consumed: alcohol Printing: drunken babble
The single-quotes inside single-quotes are required. Here's an example of it not working with double quotes:
$ sh -c "echo Consumed: $0 Printing: $#" alcohol drunken babble
Consumed: -bash Printing:
Here the values of the interactive shell's $0 and $# are replaced into the double quoted before it is passed to sh. Here's proof:
echo "Consumed: $0 Printing: $#"
Consumed: -bash Printing:
The single quotes ensure that these variables are not interpreted by interactive shell, and are passed literally to sh -c.
You could use double-quotes and \$#, but best practice is to quote your arguments (as they may contain spaces), and \"\$#\" looks even uglier, but may help you win an obfuscation contest where frazzled hair is a prerequisite for entry.
All you have to do is make a function inside an alias:
$ alias mkcd='_mkcd(){ mkdir "$1"; cd "$1";}; _mkcd'
^ * ^ ^ ^ ^ ^
You must put double quotes around "$1" because single quotes will not work. This is because clashing the quotes at the places marked with arrows confuses the system. Also, a space at the place marked with a star is needed for the function.
Once I did some fun project and I'm still using it. It's showing some animation while copy files via cp command coz cp don't show anything and it's kind of frustrating. So I've made this alias for cp:
alias cp="~/SCR/spinner cp"
And this is the spinner script
#!/bin/bash
#Set timer
T=$(date +%s)
#Add some color
. ~/SCR/color
#Animation sprites
sprite=( "(* ) ( *)" " (* )( *) " " ( *)(* ) " "( *) (* )" "(* ) ( *)" )
#Print empty line and hide cursor
printf "\n${COF}"
#Exit function
function bye { printf "${CON}"; [ -e /proc/$pid ] && kill -9 $pid; exit; }; trap bye INT
#Run our command and get its pid
"$#" & pid=$!
#Waiting animation
i=0; while [ -e /proc/$pid ]; do sleep 0.1
printf "\r${GRN}Please wait... ${YLW}${sprite[$i]}${DEF}"
((i++)); [[ $i = ${#sprite[#]} ]] && i=0
done
#Print time and exit
T=$(($(date +%s)-$T))
printf "\n\nTime taken: $(date -u -d #${T} +'%T')\n"
bye
It looks like this
Cycled animation)
Here is the link to a color script mentioned above.
And new animation cycle)
So the answer to the OP's question is to use intermediate script that could shuffle args as you wish.
An alternative solution is to use marker, a tool I've created recently that allows you to "bookmark" command templates and easily place cursor at command place-holders:
I found that most of time, I'm using shell functions so I don't have to write frequently used commands again and again in the command-line. The issue of using functions for this use case, is adding new terms to my command vocabulary and having to remember what functions parameters refer to in the real-command. Marker goal is to eliminate that mental burden.
Syntax:
alias shortName="your custom command here"
Example:
alias tlogs='_t_logs() { tail -f ../path/$1/to/project/logs.txt ;}; _t_logs'
Bash alias absolutely does accept parameters. I just added an alias to create a new react app which accepts the app name as a parameter. Here's my process:
Open the bash_profile for editing in nano
nano /.bash_profile
Add your aliases, one per line:
alias gita='git add .'
alias gitc='git commit -m "$#"'
alias gitpom='git push origin master'
alias creact='npx create-react-app "$#"'
note: the "$#" accepts parameters passed in like "creact my-new-app"
Save and exit nano editor
ctrl+o to to write (hit enter); ctrl+x to exit
Tell terminal to use the new aliases in .bash_profile
source /.bash_profile
That's it! You can now use your new aliases
Here's are three examples of functions I have in my ~/.bashrc, that are essentially aliases that accept a parameter:
#Utility required by all below functions.
#https://stackoverflow.com/questions/369758/how-to-trim-whitespace-from-bash-variable#comment21953456_3232433
alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"
.
:<<COMMENT
Alias function for recursive deletion, with are-you-sure prompt.
Example:
srf /home/myusername/django_files/rest_tutorial/rest_venv/
Parameter is required, and must be at least one non-whitespace character.
Short description: Stored in SRF_DESC
With the following setting, this is *not* added to the history:
export HISTIGNORE="*rm -r*:srf *"
- https://superuser.com/questions/232885/can-you-share-wisdom-on-using-histignore-in-bash
See:
- y/n prompt: https://stackoverflow.com/a/3232082/2736496
- Alias w/param: https://stackoverflow.com/a/7131683/2736496
COMMENT
#SRF_DESC: For "aliaf" command (with an 'f'). Must end with a newline.
SRF_DESC="srf [path]: Recursive deletion, with y/n prompt\n"
srf() {
#Exit if no parameter is provided (if it's the empty string)
param=$(echo "$1" | trim)
echo "$param"
if [ -z "$param" ] #http://tldp.org/LDP/abs/html/comparison-ops.html
then
echo "Required parameter missing. Cancelled"; return
fi
#Actual line-breaks required in order to expand the variable.
#- https://stackoverflow.com/a/4296147/2736496
read -r -p "About to
sudo rm -rf \"$param\"
Are you sure? [y/N] " response
response=${response,,} # tolower
if [[ $response =~ ^(yes|y)$ ]]
then
sudo rm -rf "$param"
else
echo "Cancelled."
fi
}
.
:<<COMMENT
Delete item from history based on its line number. No prompt.
Short description: Stored in HX_DESC
Examples
hx 112
hx 3
See:
- https://unix.stackexchange.com/questions/57924/how-to-delete-commands-in-history-matching-a-given-string
COMMENT
#HX_DESC: For "aliaf" command (with an 'f'). Must end with a newline.
HX_DESC="hx [linenum]: Delete history item at line number\n"
hx() {
history -d "$1"
}
.
:<<COMMENT
Deletes all lines from the history that match a search string, with a
prompt. The history file is then reloaded into memory.
Short description: Stored in HXF_DESC
Examples
hxf "rm -rf"
hxf ^source
Parameter is required, and must be at least one non-whitespace character.
With the following setting, this is *not* added to the history:
export HISTIGNORE="*hxf *"
- https://superuser.com/questions/232885/can-you-share-wisdom-on-using-histignore-in-bash
See:
- https://unix.stackexchange.com/questions/57924/how-to-delete-commands-in-history-matching-a-given-string
COMMENT
#HXF_DESC: For "aliaf" command (with an 'f'). Must end with a newline.
HXF_DESC="hxf [searchterm]: Delete all history items matching search term, with y/n prompt\n"
hxf() {
#Exit if no parameter is provided (if it's the empty string)
param=$(echo "$1" | trim)
echo "$param"
if [ -z "$param" ] #http://tldp.org/LDP/abs/html/comparison-ops.html
then
echo "Required parameter missing. Cancelled"; return
fi
read -r -p "About to delete all items from history that match \"$param\". Are you sure? [y/N] " response
response=${response,,} # tolower
if [[ $response =~ ^(yes|y)$ ]]
then
#Delete all matched items from the file, and duplicate it to a temp
#location.
grep -v "$param" "$HISTFILE" > /tmp/history
#Clear all items in the current sessions history (in memory). This
#empties out $HISTFILE.
history -c
#Overwrite the actual history file with the temp one.
mv /tmp/history "$HISTFILE"
#Now reload it.
history -r "$HISTFILE" #Alternative: exec bash
else
echo "Cancelled."
fi
}
References:
Trimming whitespace from strings: How to trim whitespace from a Bash variable?
Actual line breaks: https://stackoverflow.com/a/4296147/2736496
Alias w/param: https://stackoverflow.com/a/7131683/2736496 (another answer in this question)
HISTIGNORE: https://superuser.com/questions/232885/can-you-share-wisdom-on-using-histignore-in-bash
Y/N prompt: https://stackoverflow.com/a/3232082/2736496
Delete all matching items from history: https://unix.stackexchange.com/questions/57924/how-to-delete-commands-in-history-matching-a-given-string
Is string null/empty: http://tldp.org/LDP/abs/html/comparison-ops.html
Respectfully to all those saying you can't insert a parameter in the middle of an alias I just tested it and found that it did work.
alias mycommand = "python3 "$1" script.py --folderoutput RESULTS/"
when I then ran mycommand foobar it worked exactly as if I had typed the command out longhand.
NB: In case the idea isn't obvious, it is a bad idea to use aliases for anything but aliases, the first one being the 'function in an alias' and the second one being the 'hard to read redirect/source'. Also, there are flaws (which i thought would be obvious, but just in case you are confused: I do not mean them to actually be used... anywhere!)
I've answered this before, and it has always been like this in the past:
alias foo='__foo() { unset -f $0; echo "arg1 for foo=$1"; }; __foo()'
which is fine and good, unless you are avoiding the use of functions all together. in which case you can take advantage of bash's vast ability to redirect text:
alias bar='cat <<< '\''echo arg1 for bar=$1'\'' | source /dev/stdin'
They are both about the same length give or take a few characters.
The real kicker is the time difference, the top being the 'function method' and the bottom being the 'redirect-source' method. To prove this theory, the timing speaks for itself:
arg1 for foo=FOOVALUE
real 0m0.011s user 0m0.004s sys 0m0.008s # <--time spent in foo
real 0m0.000s user 0m0.000s sys 0m0.000s # <--time spent in bar
arg1 for bar=BARVALUE
ubuntu#localhost /usr/bin# time foo FOOVALUE; time bar BARVALUE
arg1 for foo=FOOVALUE
real 0m0.010s user 0m0.004s sys 0m0.004s
real 0m0.000s user 0m0.000s sys 0m0.000s
arg1 for bar=BARVALUE
ubuntu#localhost /usr/bin# time foo FOOVALUE; time bar BARVALUE
arg1 for foo=FOOVALUE
real 0m0.011s user 0m0.000s sys 0m0.012s
real 0m0.000s user 0m0.000s sys 0m0.000s
arg1 for bar=BARVALUE
ubuntu#localhost /usr/bin# time foo FOOVALUE; time bar BARVALUE
arg1 for foo=FOOVALUE
real 0m0.012s user 0m0.004s sys 0m0.004s
real 0m0.000s user 0m0.000s sys 0m0.000s
arg1 for bar=BARVALUE
ubuntu#localhost /usr/bin# time foo FOOVALUE; time bar BARVALUE
arg1 for foo=FOOVALUE
real 0m0.010s user 0m0.008s sys 0m0.004s
real 0m0.000s user 0m0.000s sys 0m0.000s
arg1 for bar=BARVALUE
This is the bottom part of about 200 results, done at random intervals. It seems that function creation/destruction takes more time than redirection. Hopefully this will help future visitors to this question (didn't want to keep it to myself).
If you're looking for a generic way to apply all params to a function, not just one or two or some other hardcoded amount, you can do that this way:
#!/usr/bin/env bash
# you would want to `source` this file, maybe in your .bash_profile?
function runjar_fn(){
java -jar myjar.jar "$#";
}
alias runjar=runjar_fn;
So in the example above, i pass all parameters from when i run runjar to the alias.
For example, if i did runjar hi there it would end up actually running java -jar myjar.jar hi there. If i did runjar one two three it would run java -jar myjar.jar one two three.
I like this $# - based solution because it works with any number of params.
There are legitimate technical reasons to want a generalized solution to the problem of bash alias not having a mechanism to take a reposition arbitrary arguments. One reason is if the command you wish to execute would be adversely affected by the changes to the environment that result from executing a function. In all other cases, functions should be used.
What recently compelled me to attempt a solution to this is that I wanted to create some abbreviated commands for printing the definitions of variables and functions. So I wrote some functions for that purpose. However, there are certain variables which are (or may be) changed by a function call itself. Among them are:
FUNCNAME
BASH_SOURCE
BASH_LINENO
BASH_ARGC
BASH_ARGV
The basic command I had been using (in a function) to print variable defns. in the form output by the set command was:
sv () { set | grep --color=never -- "^$1=.*"; }
E.g.:
> V=voodoo
sv V
V=voodoo
Problem: This won't print the definitions of the variables mentioned above as they are in the current context, e.g., if in an interactive shell prompt (or not in any function calls), FUNCNAME isn't defined. But my function tells me the wrong information:
> sv FUNCNAME
FUNCNAME=([0]="sv")
One solution I came up with has been mentioned by others in other posts on this topic. For this specific command to print variable defns., and which requires only one argument, I did this:
alias asv='(grep -- "^$(cat -)=.*" <(set)) <<<'
Which gives the correct output (none), and result status (false):
> asv FUNCNAME
> echo $?
1
However, I still felt compelled to find a solution that works for arbitrary numbers of arguments.
A General Solution To Passing Arbitrary Arguments To A Bash Aliased Command:
# (I put this code in a file "alias-arg.sh"):
# cmd [arg1 ...] – an experimental command that optionally takes args,
# which are printed as "cmd(arg1 ...)"
#
# Also sets global variable "CMD_DONE" to "true".
#
cmd () { echo "cmd($#)"; declare -g CMD_DONE=true; }
# Now set up an alias "ac2" that passes to cmd two arguments placed
# after the alias, but passes them to cmd with their order reversed:
#
# ac2 cmd_arg2 cmd_arg1 – calls "cmd" as: "cmd cmd_arg1 cmd_arg2"
#
alias ac2='
# Set up cmd to be execed after f() finishes:
#
trap '\''cmd "${CMD_ARGV[1]}" "${CMD_ARGV[0]}"'\'' SIGUSR1;
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# (^This is the actually execed command^)
#
# f [arg0 arg1 ...] – acquires args and sets up trap to run cmd:
f () {
declare -ag CMD_ARGV=("$#"); # array to give args to cmd
kill -SIGUSR1 $$; # this causes cmd to be run
trap SIGUSR1; # unset the trap for SIGUSR1
unset CMD_ARGV; # clean up env...
unset f; # incl. this function!
};
f' # Finally, exec f, which will receive the args following "ac2".
E.g.:
> . alias-arg.sh
> ac2 one two
cmd(two one)
>
> # Check to see that command run via trap affects this environment:
> asv CMD_DONE
CMD_DONE=true
A nice thing about this solution is that all the special tricks used to handle positional parameters (arguments) to commands will work when composing the trapped command. The only difference is that array syntax must be used.
E.g.,
If you want "$#", use "${CMD_ARGV[#]}".
If you want "$#", use "${#CMD_ARGV[#]}".
Etc.
I will just post my (hopefully, okay) solution
(for future readers, & most vitally; editors).
So - please edit & improve/remove anything in this post.
In the terminal:
$ alias <name_of_your_alias>_$argname="<command> $argname"
and to use it (notice the space after '_':
$<name_of_your_alias>_ $argname
for example, a alias to cat a file called hello.txt:
(alias name is CAT_FILE_)
and the $f (is the $argname, which is a file in this example)
$ alias CAT_FILE_$f="cat $f"
$ echo " " >> hello.txt
$ echo "hello there!" >> hello.txt
$ echo " " >> hello.txt
$ cat hello.txt
hello there!
Test (notice the space after '_'):
CAT_FILE_ hello.txt
As has already been pointed out by others, using a function should be considered best practice.
However, here is another approach, leveraging xargs:
alias junk="xargs -I "{}" -- mv "{}" "~/.Trash" <<< "
Note that this has side effects regarding redirection of streams.
Solution with subcommands:
d () {
if [ $# -eq 0 ] ; then
docker
return 0
fi
CMD=$1
shift
case $CMD in
p)
docker ps --all $#
;;
r)
docker run --interactive --tty $#
;;
rma)
docker container prune
docker image prune --filter "dangling=true"
;;
*)
docker $CMD $#
;;
esac
return $?
}
Using:
$ d r my_image ...
Called:
docker run --interactive --tty my_image ...
Here's the example:
alias gcommit='function _f() { git add -A; git commit -m "$1"; } ; _f'
Very important:
There is a space after { and before }.
There is a ; after each command in sequence. If you forget this after the last command, you will see > prompt instead!
The argument is enclosed in quotes as "$1"
To give specific answer to the Question posed about creating the alias to move the files to Trash folder instead of deleting them:
alias rm="mv "$1" -t ~/.Trash/"
Offcourse you have to create dir ~/.Trash first.
Then just give following command:
$rm <filename>
$rm <dirname>
Here is another approach using read. I am using this for brute search of a file by its name fragment, ignoring the "permission denied" messages.
alias loc0='( IFS= read -r x; find . -iname "*" -print 2>/dev/null | grep $x;) <<<'
A simple example:
$ ( IFS= read -r x; echo "1 $x 2 ";) <<< "a b"
1 a b 2
Note, that this converts the argument as a string into variable(s). One could use several parameters within quotes for this, space separated:
$ ( read -r x0 x1; echo "1 ${x0} 2 ${x1} 3 ";) <<< "a b"
1 a 2 b 3
Functions are indeed almost always the answer as already amply contributed and confirmed by this quote from the man page: "For almost every purpose, aliases are superseded by shell functions."
For completeness and because this can be useful (marginally more lightweight syntax) it could be noted that when the parameter(s) follow the alias, they can still be used (although this wouldn't address the OP's requirement). This is probably easiest to demonstrate with an example:
alias ssh_disc='ssh -O stop'
allows me to type smth like ssh_disc myhost, which gets expanded as expected as: ssh -O stop myhost
This can be useful for commands which take complex arguments (my memory isn't what it use t be anymore...)
For taking parameters, you should use functions!
However $# get interpreted when creating the alias instead of during the execution of the alias and escaping the $ doesn’t work either. How do I solve this problem?
You need to use shell function instead of an alias to get rid of this problem. You can define foo as follows:
function foo() { /path/to/command "$#" ;}
OR
foo() { /path/to/command "$#" ;}
Finally, call your foo() using the following syntax:
foo arg1 arg2 argN
Make sure you add your foo() to ~/.bash_profile or ~/.zshrc file.
In your case, this will work
function trash() { mv $# ~/.Trash; }
Both functions and aliases can use parameters as others have shown here. Additionally, I would like to point out a couple of other aspects:
1. function runs in its own scope, alias shares scope
It may be useful to know this difference in cases you need to hide or expose something. It also suggests that a function is the better choice for encapsulation.
function tfunc(){
GlobalFromFunc="Global From Func" # Function set global variable by default
local FromFunc="onetwothree from func" # Set a local variable
}
alias talias='local LocalFromAlias="Local from Alias"; GlobalFromAlias="Global From Alias" # Cant hide a variable with local here '
# Test variables set by tfunc
tfunc # call tfunc
echo $GlobalFromFunc # This is visible
echo $LocalFromFunc # This is not visible
# Test variables set by talias
# call talias
talias
echo $GlobalFromAlias # This is invisible
echo $LocalFromAlias # This variable is unset and unusable
Output:
bash-3.2$ # Test variables set by tfunc
bash-3.2$ tfunc # call tfunc
bash-3.2$ echo $GlobalFromFunc # This is visible
Global From Func
bash-3.2$ echo $LocalFromFunc # This is not visible
bash-3.2$ # Test variables set by talias
bash-3.2$ # call talias
bash-3.2$ talias
bash: local: can only be used in a function
bash-3.2$ echo $GlobalFromAlias # This is invisible
Global From Alias
bash-3.2$ echo $LocalFromAlias # This variable is unset and unusable
2. wrapper script is a better choice
It has happened to me several times that an alias or function can not be found when logging in via ssh or involving switching usernames or multi-user environment. There are tips and tricks with sourcing dot files, or this interesting one with alias: alias sd='sudo ' lets this subsequent alias alias install='sd apt-get install' work as expect (notice the extra space in sd='sudo '). However, a wrapper script works better than a function or alias in cases like this. The main advantage with a wrapper script is that it is visible/executable for under intended path (i.e. /usr/loca/bin/) where as a function/alias needs to be sourced before it is usable. For example, you put a function in a ~/.bash_profile or ~/.bashrc for bash, but later switch to another shell (i.e. zsh) then the function is not visible anymore.
So, when you are in doubt, a wrapper script is always the most reliable and portable solution.
alias junk="delay-arguments mv _ ~/.Trash"
delay-arguments script:
#!/bin/bash
# Example:
# > delay-arguments echo 1 _ 3 4 2
# 1 2 3 4
# > delay-arguments echo "| o n e" _ "| t h r e e" "| f o u r" "| t w o"
# | o n e | t w o | t h r e e | f o u r
RAW_ARGS=("$#")
ARGS=()
ARG_DELAY_MARKER="_"
SKIPPED_ARGS=0
SKIPPED_ARG_NUM=0
RAW_ARGS_COUNT="$#"
for ARG in "$#"; do
#echo $ARG
if [[ "$ARG" == "$ARG_DELAY_MARKER" ]]; then
SKIPPED_ARGS=$((SKIPPED_ARGS+1))
fi
done
for ((I=0; I<$RAW_ARGS_COUNT-$SKIPPED_ARGS; I++)); do
ARG="${RAW_ARGS[$I]}"
if [[ "$ARG" == "$ARG_DELAY_MARKER" ]]; then
MOVE_SOURCE_ARG_NUM=$(($RAW_ARGS_COUNT-$SKIPPED_ARGS+$SKIPPED_ARG_NUM))
MOVING_ARG="${RAW_ARGS[$MOVE_SOURCE_ARG_NUM]}"
if [[ "$MOVING_ARG" == "$ARG_DELAY_MARKER" ]]; then
echo "Error: Not enough arguments!"
exit 1;
fi
#echo "Moving arg: $MOVING_ARG"
ARGS+=("$MOVING_ARG")
SKIPPED_ARG_NUM=$(($SKIPPED_ARG_NUM+1))
else
ARGS+=("$ARG")
fi
done
#for ARG in "${ARGS[#]}"; do
#echo "ARGN: $ARG"
#done
#echo "RAW_ARGS_COUNT: $RAW_ARGS_COUNT"
#echo "SKIPPED_ARGS: $SKIPPED_ARGS"
#echo "${ARGS[#]}"
QUOTED_ARGS=$(printf ' %q' "${ARGS[#]}")
eval "${QUOTED_ARGS[#]}"

linux terminal execute echo function

when I read the book linux shell scripting cookbook
they say when you wanna print !,you shouldn't put it in double quote,or you can add \ before ! to escape it.
e.g.
$echo "Hello,world!"
bash: !:event not found error
$echo "Hello,world\\!"
Hello,world!
but in my situation(ubuntu14.04), I get the answer like that:
$echo "Hello,world!"
Hello,world!
$echo "Hello,world\\!"
Hello,world\!
So, why in my machine can't get the same answer?
Why the escape symbol \ was printed as a normal symbol?
When you're typing interactively to the shell, ! has special meaning, it's the history expansion character. To prevent this special meaning, you need to put it in single quotes or escape it.
echo 'Hello, world!'
echo "Hello, world\!'
The reason it's not happening on Ubuntu may be because it's running a newer version of bash, which is apparently more selective about when history expansion occurs. It seems to require ! to be followed by alphanumerics, not punctuation.
You don't need to do this in scripts, because history is not normally enabled there. It's just for interactive shells.
Create a shell script called file.sh:
#!/bin/bash
# file.sh: a sample shell script to demonstrate the concept of Bash shell functions
# define usage function
usage(){
echo "Usage: $0 filename"
exit 1
}
# define is_file_exits function
# $f -> store argument passed to the script
is_file_exits(){
local f="$1"
[[ -f "$f" ]] && return 0 || return 1
}
# invoke usage
# call usage() function if filename not supplied
[[ $# -eq 0 ]] && usage
# Invoke is_file_exits
if ( is_file_exits "$1" )
then
echo "File found"
else
echo "File not found"
fi
Run it as follows:
chmod +x file.sh
./file.sh
./file.sh /etc/resolv.conf

Why can't get the shell script's parameter count in a function

I'm confused about why can't get the script's parameter count in a function, could anybody help me? thanks in advance:)
test.sh
#!/bin/bash
check(){
echo $#
if [ $# -lt 2 ]; then
echo "Argument missing"
exit 1
fi
}
echo $#
check
Run:
./test.sh aa bb
output:
2
0
Functions have their own local copies of the argument variables, including $#. They are related to the arguments of the function, and the equivalents at the script level are shadowed. If you want to get the script's argument variables then you will need to either store them somewhere else first or pass them to the function.
check "$#"

How to suppress Error printed by shell commands. [duplicate]

How would I validate that a program exists, in a way that will either return an error and exit, or continue with the script?
It seems like it should be easy, but it's been stumping me.
Answer
POSIX compatible:
command -v <the_command>
Example use:
if ! command -v <the_command> &> /dev/null
then
echo "<the_command> could not be found"
exit
fi
For Bash specific environments:
hash <the_command> # For regular commands. Or...
type <the_command> # To check built-ins and keywords
Explanation
Avoid which. Not only is it an external process you're launching for doing very little (meaning builtins like hash, type or command are way cheaper), you can also rely on the builtins to actually do what you want, while the effects of external commands can easily vary from system to system.
Why care?
Many operating systems have a which that doesn't even set an exit status, meaning the if which foo won't even work there and will always report that foo exists, even if it doesn't (note that some POSIX shells appear to do this for hash too).
Many operating systems make which do custom and evil stuff like change the output or even hook into the package manager.
So, don't use which. Instead use one of these:
command -v foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
type foo >/dev/null 2>&1 || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
hash foo 2>/dev/null || { echo >&2 "I require foo but it's not installed. Aborting."; exit 1; }
(Minor side-note: some will suggest 2>&- is the same 2>/dev/null but shorter – this is untrue. 2>&- closes FD 2 which causes an error in the program when it tries to write to stderr, which is very different from successfully writing to it and discarding the output (and dangerous!))
If your hash bang is /bin/sh then you should care about what POSIX says. type and hash's exit codes aren't terribly well defined by POSIX, and hash is seen to exit successfully when the command doesn't exist (haven't seen this with type yet). command's exit status is well defined by POSIX, so that one is probably the safest to use.
If your script uses bash though, POSIX rules don't really matter anymore and both type and hash become perfectly safe to use. type now has a -P to search just the PATH and hash has the side-effect that the command's location will be hashed (for faster lookup next time you use it), which is usually a good thing since you probably check for its existence in order to actually use it.
As a simple example, here's a function that runs gdate if it exists, otherwise date:
gnudate() {
if hash gdate 2>/dev/null; then
gdate "$#"
else
date "$#"
fi
}
Alternative with a complete feature set
You can use scripts-common to reach your need.
To check if something is installed, you can do:
checkBin <the_command> || errorMessage "This tool requires <the_command>. Install it please, and then run this tool again."
The following is a portable way to check whether a command exists in $PATH and is executable:
[ -x "$(command -v foo)" ]
Example:
if ! [ -x "$(command -v git)" ]; then
echo 'Error: git is not installed.' >&2
exit 1
fi
The executable check is needed because bash returns a non-executable file if no executable file with that name is found in $PATH.
Also note that if a non-executable file with the same name as the executable exists earlier in $PATH, dash returns the former, even though the latter would be executed. This is a bug and is in violation of the POSIX standard. [Bug report] [Standard]
Edit: This seems to be fixed as of dash 0.5.11 (Debian 11).
In addition, this will fail if the command you are looking for has been defined as an alias.
I agree with lhunath to discourage use of which, and his solution is perfectly valid for Bash users. However, to be more portable, command -v shall be used instead:
$ command -v foo >/dev/null 2>&1 || { echo "I require foo but it's not installed. Aborting." >&2; exit 1; }
Command command is POSIX compliant. See here for its specification: command - execute a simple command
Note: type is POSIX compliant, but type -P is not.
It depends on whether you want to know whether it exists in one of the directories in the $PATH variable or whether you know the absolute location of it. If you want to know if it is in the $PATH variable, use
if which programname >/dev/null; then
echo exists
else
echo does not exist
fi
otherwise use
if [ -x /path/to/programname ]; then
echo exists
else
echo does not exist
fi
The redirection to /dev/null/ in the first example suppresses the output of the which program.
I have a function defined in my .bashrc that makes this easier.
command_exists () {
type "$1" &> /dev/null ;
}
Here's an example of how it's used (from my .bash_profile.)
if command_exists mvim ; then
export VISUAL="mvim --nofork"
fi
Expanding on #lhunath's and #GregV's answers, here's the code for the people who want to easily put that check inside an if statement:
exists()
{
command -v "$1" >/dev/null 2>&1
}
Here's how to use it:
if exists bash; then
echo 'Bash exists!'
else
echo 'Your system does not have Bash'
fi
Try using:
test -x filename
or
[ -x filename ]
From the Bash manpage under Conditional Expressions:
-x file
True if file exists and is executable.
To use hash, as #lhunath suggests, in a Bash script:
hash foo &> /dev/null
if [ $? -eq 1 ]; then
echo >&2 "foo not found."
fi
This script runs hash and then checks if the exit code of the most recent command, the value stored in $?, is equal to 1. If hash doesn't find foo, the exit code will be 1. If foo is present, the exit code will be 0.
&> /dev/null redirects standard error and standard output from hash so that it doesn't appear onscreen and echo >&2 writes the message to standard error.
Command -v works fine if the POSIX_BUILTINS option is set for the <command> to test for, but it can fail if not. (It has worked for me for years, but I recently ran into one where it didn't work.)
I find the following to be more failproof:
test -x "$(which <command>)"
Since it tests for three things: path, existence and execution permission.
There are a ton of options here, but I was surprised no quick one-liners. This is what I used at the beginning of my scripts:
[[ "$(command -v mvn)" ]] || { echo "mvn is not installed" 1>&2 ; exit 1; }
[[ "$(command -v java)" ]] || { echo "java is not installed" 1>&2 ; exit 1; }
This is based on the selected answer here and another source.
If you check for program existence, you are probably going to run it later anyway. Why not try to run it in the first place?
if foo --version >/dev/null 2>&1; then
echo Found
else
echo Not found
fi
It's a more trustworthy check that the program runs than merely looking at PATH directories and file permissions.
Plus you can get some useful result from your program, such as its version.
Of course the drawbacks are that some programs can be heavy to start and some don't have a --version option to immediately (and successfully) exit.
Check for multiple dependencies and inform status to end users
for cmd in latex pandoc; do
printf '%-10s' "$cmd"
if hash "$cmd" 2>/dev/null; then
echo OK
else
echo missing
fi
done
Sample output:
latex OK
pandoc missing
Adjust the 10 to the maximum command length. It is not automatic, because I don't see a non-verbose POSIX way to do it:
How can I align the columns of a space separated table in Bash?
Check if some apt packages are installed with dpkg -s and install them otherwise.
See: Check if an apt-get package is installed and then install it if it's not on Linux
It was previously mentioned at: How can I check if a program exists from a Bash script?
I never did get the previous answers to work on the box I have access to. For one, type has been installed (doing what more does). So the builtin directive is needed. This command works for me:
if [ `builtin type -p vim` ]; then echo "TRUE"; else echo "FALSE"; fi
I wanted the same question answered but to run within a Makefile.
install:
#if [[ ! -x "$(shell command -v ghead)" ]]; then \
echo 'ghead does not exist. Please install it.'; \
exit -1; \
fi
It could be simpler, just:
#!/usr/bin/env bash
set -x
# if local program 'foo' returns 1 (doesn't exist) then...
if ! type -P foo; then
echo 'crap, no foo'
else
echo 'sweet, we have foo!'
fi
Change foo to vi to get the other condition to fire.
hash foo 2>/dev/null: works with Z shell (Zsh), Bash, Dash and ash.
type -p foo: it appears to work with Z shell, Bash and ash (BusyBox), but not Dash (it interprets -p as an argument).
command -v foo: works with Z shell, Bash, Dash, but not ash (BusyBox) (-ash: command: not found).
Also note that builtin is not available with ash and Dash.
zsh only, but very useful for zsh scripting (e.g. when writing completion scripts):
The zsh/parameter module gives access to, among other things, the internal commands hash table. From man zshmodules:
THE ZSH/PARAMETER MODULE
The zsh/parameter module gives access to some of the internal hash ta‐
bles used by the shell by defining some special parameters.
[...]
commands
This array gives access to the command hash table. The keys are
the names of external commands, the values are the pathnames of
the files that would be executed when the command would be in‐
voked. Setting a key in this array defines a new entry in this
table in the same way as with the hash builtin. Unsetting a key
as in `unset "commands[foo]"' removes the entry for the given
key from the command hash table.
Although it is a loadable module, it seems to be loaded by default, as long as zsh is not used with --emulate.
example:
martin#martin ~ % echo $commands[zsh]
/usr/bin/zsh
To quickly check whether a certain command is available, just check if the key exists in the hash:
if (( ${+commands[zsh]} ))
then
echo "zsh is available"
fi
Note though that the hash will contain any files in $PATH folders, regardless of whether they are executable or not. To be absolutely sure, you have to spend a stat call on that:
if (( ${+commands[zsh]} )) && [[ -x $commands[zsh] ]]
then
echo "zsh is available"
fi
The which command might be useful. man which
It returns 0 if the executable is found and returns 1 if it's not found or not executable:
NAME
which - locate a command
SYNOPSIS
which [-a] filename ...
DESCRIPTION
which returns the pathnames of the files which would
be executed in the current environment, had its
arguments been given as commands in a strictly
POSIX-conformant shell. It does this by searching
the PATH for executable files matching the names
of the arguments.
OPTIONS
-a print all matching pathnames of each argument
EXIT STATUS
0 if all specified commands are
found and executable
1 if one or more specified commands is nonexistent
or not executable
2 if an invalid option is specified
The nice thing about which is that it figures out if the executable is available in the environment that which is run in - it saves a few problems...
Use Bash builtins if you can:
which programname
...
type -P programname
For those interested, none of the methodologies in previous answers work if you wish to detect an installed library. I imagine you are left either with physically checking the path (potentially for header files and such), or something like this (if you are on a Debian-based distribution):
dpkg --status libdb-dev | grep -q not-installed
if [ $? -eq 0 ]; then
apt-get install libdb-dev
fi
As you can see from the above, a "0" answer from the query means the package is not installed. This is a function of "grep" - a "0" means a match was found, a "1" means no match was found.
This will tell according to the location if the program exist or not:
if [ -x /usr/bin/yum ]; then
echo "This is Centos"
fi
I'd say there isn't any portable and 100% reliable way due to dangling aliases. For example:
alias john='ls --color'
alias paul='george -F'
alias george='ls -h'
alias ringo=/
Of course, only the last one is problematic (no offence to Ringo!). But all of them are valid aliases from the point of view of command -v.
In order to reject dangling ones like ringo, we have to parse the output of the shell built-in alias command and recurse into them (command -v isn't a superior to alias here.) There isn't any portable solution for it, and even a Bash-specific solution is rather tedious.
Note that a solution like this will unconditionally reject alias ls='ls -F':
test() { command -v $1 | grep -qv alias }
If you guys/gals can't get the things in answers here to work and are pulling hair out of your back, try to run the same command using bash -c. Just look at this somnambular delirium. This is what really happening when you run $(sub-command):
First. It can give you completely different output.
$ command -v ls
alias ls='ls --color=auto'
$ bash -c "command -v ls"
/bin/ls
Second. It can give you no output at all.
$ command -v nvm
nvm
$ bash -c "command -v nvm"
$ bash -c "nvm --help"
bash: nvm: command not found
#!/bin/bash
a=${apt-cache show program}
if [[ $a == 0 ]]
then
echo "the program doesn't exist"
else
echo "the program exists"
fi
#program is not literal, you can change it to the program's name you want to check
The hash-variant has one pitfall: On the command line you can for example type in
one_folder/process
to have process executed. For this the parent folder of one_folder must be in $PATH. But when you try to hash this command, it will always succeed:
hash one_folder/process; echo $? # will always output '0'
I second the use of "command -v". E.g. like this:
md=$(command -v mkdirhier) ; alias md=${md:=mkdir} # bash
emacs="$(command -v emacs) -nw" || emacs=nano
alias e=$emacs
[[ -z $(command -v jed) ]] && alias jed=$emacs
I had to check if Git was installed as part of deploying our CI server. My final Bash script was as follows (Ubuntu server):
if ! builtin type -p git &>/dev/null; then
sudo apt-get -y install git-core
fi
To mimic Bash's type -P cmd, we can use the POSIX compliant env -i type cmd 1>/dev/null 2>&1.
man env
# "The option '-i' causes env to completely ignore the environment it inherits."
# In other words, there are no aliases or functions to be looked up by the type command.
ls() { echo 'Hello, world!'; }
ls
type ls
env -i type ls
cmd=ls
cmd=lsx
env -i type $cmd 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; }
If there isn't any external type command available (as taken for granted here), we can use POSIX compliant env -i sh -c 'type cmd 1>/dev/null 2>&1':
# Portable version of Bash's type -P cmd (without output on stdout)
typep() {
command -p env -i PATH="$PATH" sh -c '
export LC_ALL=C LANG=C
cmd="$1"
cmd="`type "$cmd" 2>/dev/null || { echo "error: command $cmd not found; exiting ..." 1>&2; exit 1; }`"
[ $? != 0 ] && exit 1
case "$cmd" in
*\ /*) exit 0;;
*) printf "%s\n" "error: $cmd" 1>&2; exit 1;;
esac
' _ "$1" || exit 1
}
# Get your standard $PATH value
#PATH="$(command -p getconf PATH)"
typep ls
typep builtin
typep ls-temp
At least on Mac OS X v10.6.8 (Snow Leopard) using Bash 4.2.24(2) command -v ls does not match a moved /bin/ls-temp.
My setup for a Debian server:
I had the problem when multiple packages contained the same name.
For example apache2. So this was my solution:
function _apt_install() {
apt-get install -y $1 > /dev/null
}
function _apt_install_norecommends() {
apt-get install -y --no-install-recommends $1 > /dev/null
}
function _apt_available() {
if [ `apt-cache search $1 | grep -o "$1" | uniq | wc -l` = "1" ]; then
echo "Package is available : $1"
PACKAGE_INSTALL="1"
else
echo "Package $1 is NOT available for install"
echo "We can not continue without this package..."
echo "Exitting now.."
exit 0
fi
}
function _package_install {
_apt_available $1
if [ "${PACKAGE_INSTALL}" = "1" ]; then
if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then
echo "package is already_installed: $1"
else
echo "installing package : $1, please wait.."
_apt_install $1
sleep 0.5
fi
fi
}
function _package_install_no_recommends {
_apt_available $1
if [ "${PACKAGE_INSTALL}" = "1" ]; then
if [ "$(dpkg-query -l $1 | tail -n1 | cut -c1-2)" = "ii" ]; then
echo "package is already_installed: $1"
else
echo "installing package : $1, please wait.."
_apt_install_norecommends $1
sleep 0.5
fi
fi
}

busybox sh wrapper to add extra functionality

I need a simple busybox sh wrapper which will do:
IF "-Q" PARAMETER IS PROVIDED THEN
acommand ALL PARAMETERS BUT "-Q" 2>&1 1>/dev/null
ELSE
acommand ALL PARAMETERS
FI
Parameters may include spaces.
BTW I want to run the script with busybox sh and it doesn't support arrays.
It's possible to do it all in busybox's ash shell:
#!/bin/sh
for i in "${#}"
do
if [ "$i" = "-Q" ]
then
flagQ=1
else
args="$args \"$i\""
fi
done
if [ "$flagQ" = "1" ]
then
eval acommand "$args" 2>&1 1>/dev/null
else
eval acommand "$args"
fi
This uses bash arrays - but I see from the comments to another answer that the code isn't supposed to run under bash (despite the bash tag originally applied to the question); it is meant to run under the busybox shell.
I'm almost certain it doesn't answer the question because the question is substantially unanswerable given the limitations of busybox. In times past, I have used a custom program I called 'escape' to build up an argument string that can be eval'd to get the original arguments - spaces and all. But that requires support from outside the shell.
This solution only uses 'bash'. I'm not sure it is fully idiomatic bash code, but it works.
#!/bin/bash
i=0
Qflag=0
for arg in "$#"
do
if [ "X$arg" = "X-Q" ]
then Qflag=1
else args[$((i++))]=$arg
fi
done
if [ $Qflag = 1 ]
then exec acommand "${args[#]}" 2>&1 >/dev/null
else exec acommand "${args[#]}"
fi
The first loops builds up an array, args, with the arguments to the script, except it doesn't add '-Q' to the list and records its presence in variable Qflag.
The if statement at the end notes whether Qflag was set to 1, and if so, sends the errors from 'acommand' to standard output and sends regular standard output to /dev/null (which is different from the effect if the I/O redirections are reversed - that would send standard output to /dev/null and send standard error to the same place, forcing silence on 'acommand').
The use of 'exec' is a trivial optimization that simplifies exit status handling in this case.
Tested with 'acommand' that prints its arguments on separate lines:
#!/bin/sh
for arg in "$#"
do echo "$arg"
done
and with command lines such as:
bash wrapper.sh -c -d 'arg with spaces'
which produces the output:
-c
-d
arg with spaces
Obviously, with the I/O redirection in place, there is no output from:
bash wrapper.sh -c -Q -d 'arg with spaces'
However, if you omit the I/O redirection, you get to see the same output.
It's a pity that you need to handle spaces in the arguments otherwise this might work:
#!/bin/sh
Q=0
ARGS=
while [ $# -ge 1 ]; do
case $1 in
-Q)
Q=1
;;
*)
ARGS="$ARGS $1"
;;
esac
shift
done
if [ $Q -eq 1 ] ; then
acommand $ARGS 2>&1 1>/dev/null
else
acommand $ARGS
fi
EDIT:
So this version handles spaces, at the expense of interpreting back-ticks.
#!/bin/busybox ash
Q=0
ARGS=
while [ $# -ge 1 ]; do
case $1 in
-Q)
Q=1
;;
*)
ARGS="$ARGS \"$1\""
;;
esac
shift
done
if [ "$Q" -eq 1 ] ; then
eval acommand $ARGS 2>&1 1>/dev/null
else
eval acommand $ARGS
fi
I think to have a complete solution you are going to have to code it in C, which will be a bit ugly.

Resources