How can I use a positional parameter as a suffix when making a directory? - string

I'm writing a small bash script and I'm trying to create a directory by this:
mkdir ~/deploy.$1
I would think it should produce deploy.scriptFoo or what ever the valuable of $1 is.
It's only producing "deploy." and leaving off the $1 variable. I have tested the $1 variable in the output and I am positive it is being passed into the script. Any ideas?

The Problem
The $1 position parameter is the first argument to your script, not the name of the script itself.
The Solution
If you want the script name, use $0. For example, given this sample script stored in /tmp/param_test.sh:
#!/bin/bash
mkdir "/tmp/deploy.$(basename "$0" .sh)"
ls -d /tmp/deploy*
the script ignores any arguments, but correctly returns the following output:
/tmp/deploy.param_test
If you want to vary the name, then you have to use a positional parameter in your script and call the script with an argument. For example:
#!/bin/bash
mkdir "/tmp/deploy.$1"
ls -d /tmp/deploy*
On the command line, you pass the argument to your script. For example:
$ bash /tmp/param_test.sh foo
/tmp/deploy.foo
See Also
http://www.gnu.org/software/bash/manual/bashref.html#Positional-Parameters

Related

How to pass argument in the custom bash function as part of a dir path?

I want to define a custom bash function, which gets an argument as a part of a dir path.
I'm new to bash scripts. The codes provided online are somehow confusing for me or don't work properly.
For example, the expected bash script looks like:
function my_copy() {
sudo cp ~/workspace/{$1} ~/tmp/{$2}
}
If I type my_copy a b,
then I expect the function executes sudo cp ~/workspace/a ~/tmp/b
in the terminal.
Thanks in advance.
If you have the below function in say copy.sh file and if you source it ( source copy.sh or . copy.sh) then the function call my_copy will work as expected.
$1 and $2 are positional parameters.
i.e. when you call my_copy a b, $1 will have the first command line argument as its value which is a in your case and $2 which is second command line argument, will have the value b. The function will work as expected.
Also you have a logical error in the function, you have given {$1} instead of ${1}. It will expand to {a} instead of a in your function and it will throw an error that says cp: cannot stat '~/workspace/{a}': No such file or directory when you run it.
Additionally, if the number of positional parameters are greater than 10, only then it is required to use {} in between otherwise you can avoid it. eg: ${11} instead of $11.
function my_copy() {
sudo cp ~/workspace/$1 ~/tmp/$2
}
So above function will execute the statement sudo cp ~/workspace/a ~/tmp/b as expected.
To understand the concept, you can try echo $1, echo ${1}, echo {$1}, echo {$2}, echo ${2} and echo $2 inside the script to see the resulting values. For more special $ sign shell variables
There is a syntax error in your code. You don't call a variable like {$foo}. If 1=a and 2=b then you execute
sudo cp ~/workspace/{$1} ~/tmp/{$2}
BASH is going to replace $1 with a and $2 with b, so, BASH is going to execute
sudo cp ~/workspace/{a} ~/tmp/{b}
That means tha cp is going to fail because there is no file with a name like {a}
There is some ways to call a variable
echo $foo
echo ${foo}
echo "$foo"
echo "${foo}"
Otherwise, your code looks good, should work.
Take a look a this links first and second, it's really important to quoting your variables. If you want more information about BASH or can't sleep at night try with the Official Manual, it have everything you must know about BASH and it's a good somniferous too ;)
PS: I know $1, $2, etc are positional parameters, I called it variables because you treat it as a variable, and my anwser can be applied for both.

how to pass asterisk into ls command inside bash script

Hi… Need a little help here…
I tried to emulate the DOS' dir command in Linux using bash script. Basically it's just a wrapped ls command with some parameters plus summary info. Here's the script:
#!/bin/bash
# default to current folder
if [ -z "$1" ]; then var=.;
else var="$1"; fi
# check file existence
if [ -a "$var" ]; then
# list contents with color, folder first
CMD="ls -lgG $var --color --group-directories-first"; $CMD;
# sum all files size
size=$(ls -lgGp "$var" | grep -v / | awk '{ sum += $3 }; END { print sum }')
if [ "$size" == "" ]; then size="0"; fi
# create summary
if [ -d "$var" ]; then
folder=$(find $var/* -maxdepth 0 -type d | wc -l)
file=$(find $var/* -maxdepth 0 -type f | wc -l)
echo "Found: $folder folders "
echo " $file files $size bytes"
fi
# error message
else
echo "dir: Error \"$var\": No such file or directory"
fi
The problem is when the argument contains an asterisk (*), the ls within the script acts differently compare to the direct ls command given at the prompt. Instead of return the whole files list, the script only returns the first file. See the video below to see the comparation in action. I don't know why it behaves like that.
Anyone knows how to fix it? Thank you.
Video: problem in action
UPDATE:
The problem has been solved. Thank you all for the answers. Now my script works as expected. See the video here: http://i.giphy.com/3o8dp1YLz4fIyCbOAU.gif
The asterisk * is expanded by the shell when it parses the command line. In other words, your script doesn't get a parameter containing an asterisk, it gets a list of files as arguments. Your script only works with $1, the first argument. It should work with "$#" instead.
This is because when you retrieve $1 you assume the shell does NOT expand *.
In fact, when * (or other glob) matches, it is expanded, and broken into segments by $IFS, and then passed as $1, $2, etc.
You're lucky if you simply retrieved the first file. When your first file's path contains spaces, you'll get an error because you only get the first segment before the space.
Seriously, read this and especially this. Really.
And please don't do things like
CMD=whatever you get from user input; $CMD;
You are begging for trouble. Don't execute arbitrary string from the user.
Both above answers already answered your question. So, i'm going a bit more verbose.
In your terminal is running the bash interpreter (probably). This is the program which parses your input line(s) and doing "things" based on your input.
When you enter some line the bash start doing the following workflow:
parsing and lexical analysis
expansion
brace expansion
tidle expansion
variable expansion
artithmetic and other substitutions
command substitution
word splitting
filename generation (globbing)
removing quotes
Only after all above the bash
will execute some external commands, like ls or dir.sh... etc.,
or will do so some "internal" actions for the known keywords and builtins like echo, for, if etc...
As you can see, the second last is the filename generation (globbing). So, in your case - if the test* matches some files, your bash expands the willcard characters (aka does the globbing).
So,
when you enter dir.sh test*,
and the test* matches some files
the bash does the expansion first
and after will execute the command dir.sh with already expanded filenames
e.g. the script get executed (in your case) as: dir.sh test.pas test.swift
BTW, it acts exactly with the same way for your ls example:
the bash expands the ls test* to ls test.pas test.swift
then executes the ls with the above two arguments
and the ls will print the result for the got two arguments.
with other words, the ls don't even see the test* argument - if it is possible - the bash expands the wilcard characters. (* and ?).
Now back to your script: add after the shebang the following line:
echo "the $0 got this arguments: $#"
and you will immediatelly see, the real argumemts how your script got executed.
also, in such cases is a good practice trying to execute the script in debug-mode, e.g.
bash -x dir.sh test*
and you will see, what the script does exactly.
Also, you can do the same for your current interpreter, e.g. just enter into the terminal
set -x
and try run the dir.sh test* = and you will see, how the bash will execute the dir.sh command. (to stop the debug mode, just enter set +x)
Everbody is giving you valuable advice which you should definitely should follow!
But here is the real answer to your question.
To pass unexpanded arguments to any executable you need to single quote them:
./your_script '*'
The best solution I have is to use the eval command, in this way:
#!/bin/bash
cmd="some command \"with_quetes_and_asterisk_in_it*\""
echo "$cmd"
eval $cmd
The eval command takes its arguments and evaluates them into the command as the shell does.
This solves my problem when I need to call a command with asterisk '*' in it from a script.

Bash: execute a multi-command line string in a script

There is, in a file, some multi-command line like this:
cd /home/user; ls
In a bash script, I would like to execute these commands, adding some arguments to the last one. For example:
cd /home/user; ls -l *.png
I thought it would be enough to do something like this:
#!/bin/bash
commandLine="$(cat theFileWithCommandInside) -l *.png"
$commandLine
exit 0
But it says:
/home/user;: No such file or directory
In other words, the ";" character doesn't mean anymore "end of the command": The shell is trying to find a directory called "user;" in the home folder...
I tried to replace ";" with "&&", but the result is the same.
the point of your question is to execute command stored in string. there are thousands of ways to execute that indirectly. but eventually, bash has to involve.
so why not explicitly invoke bash to do the job?
bash -c "$commandLine"
from doc:
-c string
If the -c option is present, then commands are read from string. If there are arguments after the string, they are assigned to the positional parameters, starting with $0.
http://linux.die.net/man/1/bash
Why dont you execute the commands themselves in the script, instead of "importing" them?
#!/bin/bash
cd /home/user; ls -l *.png
exit 0
Wrap the command into a function:
function doLS() {
cd user; ls $#
}
$# expands to all arguments passed to the function. If you (or the snippet authors) add functions expecting a predefined number of arguments, you may find the positional parameters $1, $2, ... useful instead.
As the maintainer of the main script, you will have to make sure that everyone providing such a snippet provides that "interface" your code uses (i.e. their code defines the functions your program calls and their functions process the arguments your program passes).
Use source or . to import the function into your running shell:
#!/bin/bash
source theFileWithCommandInside
doLS -l *.png
exit 0
I'd like to add a few thoughts on the ; topic:
In other words, the ";" character doesn't mean anymore "end of the
command": The shell is trying to find a directory called "user;" in
the home folder...
; is not used to terminate a statement as in C-style languages. Instead it is used to separate commands that should be executed sequentially inside a list. Example executing two commands in a subshell:
( command1 ; command2 )
If the list is part of a group, it must be succeeded by a ;:
{ command1 ; command2 ; }
In your example, tokenization and globbing (replacing the *) will not be executed (as you may have expected), so your code will not be run successfully.
The key is: eval
Here, the fixed script (look at the third line):
#!/bin/bash
commandLine="$(cat theFileWithCommandInside) -l *.png"
eval $commandLine
exit 0
Using the <(...) form
sh <(sed 's/$/ *.png/' theFileWithCommandInside)

A way to get run directory name

I can't understand the following code in a bash.
set `pwd` ; mfix=$1
It actually get the run directory name.But I don't how does it work.
What is the set command mean?
From the doc for the set:
This builtin is so complicated that it deserves its own section. set
allows you to change the values of shell options and set the
positional parameters, or to display the names and values of shell
variables.
e.g.
set v1 v2 v3 ; echo $1
will print
v1
The comand inside backticks is called as "command substitution". From the docs:
Bash performs the expansion by executing command and replacing the
command substitution with the standard output of the command, with any
trailing newlines deleted.
In your example, it sets the 1st positional argument $1 to the value of the result of execution of command inside the backticks. (called as command substitution). The command is pwd what shows the current working directory.
Anyway, if the path to the directory contains an space, the $1 will get only the first part of the path., e.g.
$ pwd
/some/path with/space
$ set `pwd`
$ echo $1
/some/path
$echo $2
with/space
Finally the all above is strange design, because you can simply:
mfix=$(pwd) #old school: mfix=`pwd`
It is better to use the $(command) instead of the backticks.
This code in bash put the result of the command pwd in the variable mfix.
You can print the result of the mfix variable by running
echo $mfix

shell string bad substitution

I'm new to shell programming. I intend to get directory name after zip file was extracted. The print statement of it is
$test.sh helloworld.zip
helloworld
Let's take a look at test.sh:
#! /bin/sh
length=echo `expr index "$1" .zip`
a=$1
echo $(a:0:length}
However I got the Bad substitution error from the compiler.
And when I mention about 'shell'.I just talking about shell for I don't know the difference between bash or the others.I just using Ubuntu 10.04 and using the terminal. (I am using bash.)
If your shell is a sufficiently recent version of bash, that parameter expansion notation should work.
In many other shells, it will not work, and a bad substitution error is the way the shell says 'You asked for a parameter substitution but it does not make sense to me'.
Also, given the script:
#! /bin/sh
length=echo `expr index "$1" .zip`
a=$1
echo $(a:0:length}
The second line exports variable length with value echo for the command that is generated by running expr index "$1" .zip. It does not assign to length. That should be just:
length=$(expr index "${1:?}" .zip)
where the ${1:?} notation generates an error if $1 is not set (if the script is invoked with no arguments).
The last line should be:
echo ${a:0:$length}
Note that if $1 holds filename.zip, the output of expr index $1 .zip is 2, because the letter i appears at index 2 in filename.zip. If the intention is to get the base name of the file without the .zip extension, then the classic way to do it is:
base=$(basename $1 .zip)
and the more modern way is:
base=${1%.zip}
There is a difference; if the name is /path/to/filename.zip, the classic output is filename and the modern one is /path/to/filename. You can get the classic output with:
base=${1%.zip}
base=${base##*/}
Or, in the classic version, you can get the path with:
base=$(dirname $1)/$(basename $1 .zip)`.)
If the file names can contain spaces, you need to think about using double quotes, especially in the invocations of basename and dirname.
Try running it with bash.
bash test.sh helloworld.zip
-likewise-
"try changing the first line to #!/bin/bash" as comment-answered by – #shellter
Try that in bash :
echo $1
len=$(wc -c <<< "$1")
a="${1}.zip"
echo ${a:0:$len}
Adapt it to fit your needs.

Resources