A way to get run directory name - linux

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

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.

One line setting environment variable and execute command, got different results in sh and bash

I'm trying to figure out, how the oneliner
var=value command
actually works in sh. I expect variable var to be passed to the environment of command, but shouldn't exist in the current environment (please, do not omit disclaimer at the end!)
First, let's try it in bash
#bash
$ var= #just to be sure it's empty
$ var=value echo something
. something
$ echo "$var"
.
$ var=value set something
$ echo "$var"
.
For now, it works as expected. But when we go to sh and retype the same input, it'll be like that:
#sh
$ var=
$ var=value echo something
. something
$ echo "$var"
.
$ var=value set something
$ echo "$var"
. value
And the last one differs. Is set command some kind of special case for sh? Why has the variable var been saved in our current environment?
disclaimer: I know that echo and set are shell built-in's and thus environment variables we pass them by var=value command are wasted, but my question is about the syntax only. I mean, they should have been wasted, but in sh, when typed set, the variable was somehow passed into the current environment.
set is a "special" built-in, defined as such in the POSIX specification.
As described in Simple Commands, variable assignments preceding the invocation of a special built-in utility remain in effect after the built-in completes; this shall not be the case with a regular built-in or other utility.
So in this case, bash is actually in violation of the POSIX specification. Running in POSIX mode, though, it behaves the same as sh:
$ bash --posix
$ var=value set something
$ echo $var
value

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.

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.

Why doesn't this bash code work?

x="a=b"
`echo $x`
echo $a
I expect the second line to generate "a=b", and execute it in the context of the main shell, resulting in a new variable a with value b.
However, what I really get (if I enter the commands manually) is the error message after the second line, bash: a=b: command not found
Why is that so?
Try
eval $x
(And we need 30 characters for this answer to be posted)
What your first echo line does is running in a subshell and returns its value to the callee.. The same result is achieved using $() and is - by the way - easier to use than backticks.
So, what you are doing is first running echo $x (which returns a=b). And, because of the backticks, a=b is returned to the shell that tries to run that line as a command which - obviously - won't work.
Try this in a shell:
$(echo ls)
And you will clearly see what is happening.
It's because of the order in which bash parses the command line. It looks for variable definitions (e.g. a=b) before performing variable and command substitution (e.g. commands in backticks). Because of this, by the time echo $x is replaced by a=b, it's too late for bash to see this as a variable definition and it's parsed as a command instead. The same thing would've happened if you'd just used $x as the command (instead of echo in backticks). As in #mvds's answer, the eval command can be used to force the command to be reparsed from the beginning, meaning that it'll be recognized as a variable definition:
$ x="a=b"
$ `echo $x`
-bash: a=b: command not found
$ $(echo $x) # Exact same thing, but with cleaner syntax
-bash: a=b: command not found
$ $x # This also does the same thing, but without some extra steps
-bash: a=b: command not found
$ eval "$x" # This will actually work
$ echo $a
b
$ a= # Start over
$ eval "$(echo "$x")" # Another way of doing the same thing, with extra steps
$ echo $a
b
Note that when using eval I've put all of the references to $x in double-quotes -- this is to prevent the later phases of bash parsing (e.g. word splitting) from happening twice, since bash will finish its regular parsing process, then recognize the eval command, and then redo the entire parsing process again. It's really easy to get unexpected results from using eval, and this removes at least some of the potential for trouble.
Did you try $x in that funny apostrophes? Without echo, echo seems to be only for displaying string, not execute commands.

Resources