Shell script : changing working dir and spaces in folder name - bash

I want to make a script that takes a file path for argument, and cds into its folder.
Here is what I made :
#!/bin/bash
#remove the file name, and change every space into \space
shorter=`echo "$1" | sed 's/\/[^\/]*$//' | sed 's/\ /\\\ /g'`
echo $shorter
cd $shorter
I actually have 2 questions (I am a relative newbie to shell scripts) :
How could I make the cd "persistent" ? I want to put this script into /usr/bin, and then call it from wherever in the filesystem. Upon return of the script, I want to stay in the $shorter folder. Basically, if pwd was /usr/bin, I could make it by typing . script /my/path instead of ./script /my/path, but what if I am in an other folder ?
The second question is trickier. My script fails whenever there is a space in the given argument. Although $shorter is exactly what I want (for instance /home/jack/my\ folder/subfolder), cd fails whith the error /usr/bin/script : line 4 : cd: /home/jack/my\: no file or folder of this type. I think I have tried everything, using things like cd '$shorter' or cd "'"$shorter"'" doesn't help. What am I missing ??
Thanks a lot for your answers

in your .bashrc add the following line:
function shorter() { cd "${1%/*}"; }
% means remove the smaller pattern from the end
/* is the patern
Then in your terminal:
$ . ~/.bashrc # to refresh your bash configuration
$ type shorter # to check if your new function is available
shorter is a function
shorter ()
{
cd "${1%/*}"
}
$ shorter ./your/directory/filename # this will move to ./your/directory

The first part:
The change of directory won't be “persistent” beyond the lifetime of your script, because your script runs in a new shell process. You could, however, use a shell alias or a shell function. For example, you could embed the code in a shell function and define it in your .bash_profile or other source location.
mycdfunction () {
cd /blah/foo/"$1"
}
As for the “spaces in names” bit:
The general syntax for referring to a variable in Bourne shells is: "$var" — the "double quotes" tell the shell to expand any variables inside of them, but to group the outcome as a single parameter.
Omitting the double quotes around $var tells the shell to expand the variable, but then split the results into parameters (“words”) on whitespace. This is how the shell splits up parameters, normally.
Using 'single quotes' causes the shell to not expand any contents, but group the parameters togethers.
You can use \ (backslash-blank) to escape a space when you're typing (or in a script), but that's usually harder to read than using 'single quotes' or "double quotes"…
Note that the expansion phase includes: $variables wild?cards* {grouping,names}with-braces $(echo command substitution) and other effects.
| expansion | no expansion
-------------------------------------------------------
grouping | " " | ' '
splitting | (no punc.) | (not easily done)

For the first part, there is no need for the shorter variable at all. You can just do:
#!/bin/bash
cd "${1%/*}"
Explanation
Most shells, including bash, have what is called Parameter Expansion and they are very powerful and efficient as they allow you to manipulate variables nativly within the shell that would normally require a call to an external binary.
Two common examples of where you can use Parameter Expansion over an external call would be:
${var%/*} # replaces dirname
${var##*/} # replaces basename
See this FAQ on Parameter Expansion to learn more. In fact, while you're there might as well go over the whole FAQ

When you put your script inside /usr/bin you can call it anywhere. And to deal with whitespace in the shell just put the target between "" (but this doesn't matter !!).
Well here is a demo:
#!/bin/bash
#you can use dirname but that's not apropriate
#shorter=$(dirname $1)
#Use parameter expansion (too much better)
shorter=${1%/*}
echo $shorter

An alternate way to do it, since you have dirname on your Mac:
#!/bin/sh
cd "$(dirname "$1")"
Since you mentioned in the comments that you wanted to be able to drag files into a window and cd to them, you might want to make your script allow file or directory paths as arguments:
#!/bin/sh
[ -f "$1" ] && set "$(dirname "$1")" # convert a file to a directory
cd "$1"

Related

creating multiple directories based on value of variable

I have variable "one" which contains following
avi,mkw,dvd,cd
im trying to dynamicly create directories that would look like this
type-avi
type-mkw
type-dvd
type-cd
I have tried to achieve wanted result with following code
mkdir type-{"$one"}
but instead of creating 4 directories , it created one directory called
type-{avi,mkw,dvd,cd}
I suppose this is wrong method.. if so , how can i create dynamicly directories with "suffixes" stored in variabe?
Use an array instead of your string variable for this.
IFS=, read -a onearr <<<"$one"
mkdir "${onearr[#]/#/type-}"
Or if you don't need the $one string in the first place just create the array manually.
onearr=(avi mkw dvd cd)
mkdir "${onearr[#]/#/type-}"
If you aren't worried about spaces or anything in the values in $one and can trust your input to be "safe" and not exploitative and can't use read then you could use this to create the array instead (but it is just flat out a worse soluton).
onearr=($(tr , ' ' <<<"$one"))
A way to do this without reading into the shell, in a traditional tools pipeline approach:
echo "$one" |
tr ',' '\n' |
sed "s/^/mkdir 'type-/; s/$/'/" |
sh -x
Your original attempt was very close. To make it work, you can use the shell eval command:
eval mkdir type-{$one}
or
echo mkdir type{"$one"} | bash
In either case, the effect causes bash to re-evaluate the line.
I personally would not recommend this approach for these reasons:
eval can be a security risk and is little used, maintainers will have to do a double-take.
Brace Expansion is a bash-type shell extension and while I love bash, I write all shell scripts to run with the POSIX /bin/sh.
These will not handle unusual characters in filenames, such as spaces.
The eval causes the shell to re-evaluate the string after the variable substition has been performed. To gain more understanding on these topics, see "Brace Expansion" and also the eval command, both on the bash man page.

how to find last selected character in shell script

I have assigned following string to a variable.
line="/remotepath/mypath/localpath/common/location.txt"
If I want to access common location (/remotepath/mypath/localpath/common)
how can I split this in last "/" ?
In most unix-style operating systems, there's a program called dirname which does this for you:
$ line="/remotepath/mypath/localpath/common/location.txt"
$ dirname "$line"
/remotepath/mypath/localpath/common
The command is of course available from any shell, since it's not part of the shell per-se, though you might need to assign the variable differently. For example, in csh/tcsh:
% setenv line "/remotepath/mypath/localpath/common/location.txt"
% dirname "$line"
/remotepath/mypath/localpath/common
If you want to strip off the file using shell commands alone, you'll need to specify what shell you're using, since commands vary. For example, in /bin/sh or similar shells (like bash), you could use "Parameter expansion" (look it up in the man page, there's lots of good stuff):
$ line="/remotepath/mypath/localpath/common/location.txt"
$ echo "${line%/*}
/remotepath/mypath/localpath/common
Hey you can use below command if your line variable contains same number of directories always
echo $line | cut -d "/" -f1-5
line="/remotepath/mypath/localpath/common/location.txt"
path="${line%/*}"
file="${line##*/}"
## contents of the variables after extraction
# path is '/remotepath/mypath/localpath/common'
# file is 'location.txt'
It's called parameter expansion/substring extraction in bash.

Faster way to Unix cd by changing one directory inside path?

I have multiple directories (eg tom richard harry) that have identical subdirectory and file structure. If I am working on a file inside one directory, is there a fast or easy way to cd to the equivalent path in another directory?
Example
pwd=/mystuff/myproject/tom/hobbies/sports/highschool
cd /mystuff/myproject/richard/hobbies/sports/highschool
I was hoping for some shortcut like cd pwd but change tom > richard in one command.
The following should work:
cd ${PWD/tom/richard}
This would work:
cd $(pwd | perl -pi -e 's/tom/richard/g;')
If you know what directory you're in (say stored in $dirname variable):
function dirswitch() {
newdir="$1"
cd $(pwd | sed -e "s#/$dirname/#/$newdir/#")
}
This should handle the job in bash. So if you're in dirname=tom and you want to switch to harry:
dirswitch harry
...will do the trick.
You can use bash's history expansion for this.
^tom^richard - this will rerun the previous command, substituting richard for tom.
Bash History Expansion
It all depends upon your shell...
Most people use BASH -- it's the standard Linux shell, but Kornshell is very similar to BASH, and has the feature you're looking for:
$ cd /mystuff/myproject/tom/hobbies/sports/highschool
$ cd tom richard
$ pwd
/mystuff/myproject/richard/hobbies/sports/highschool
I also like the Kornshell print command and the way variables in Kornshell don't disappear on you in loops (because BASH makes them child processes).
Of course, BASH has features that are missing in Kornshell. One example is setting your prompt. In Bash, I set my prompt as thus:
PS1="\u#\h:\w\n\$ "
\u is the user ID
\h is the short host name
\w is the working directory in relationship to $HOME
\n is the newline
\$ is a $ if your ID isn't root and # if your ID is root.
The Kornshell equivalent is:
PS1=$(print -n "logname#hostname:";if [[ "${PWD#$HOME}" != "$PWD" ]] then; print -n "~${PWD#$HOME}"; else; print -n "$PWD";fi;print "\n$ ")
As I said, they're mostly equivalent. I can work with either one, but Kornshell has this particular feature and BASH doesn't.
Your alternative is to write a function that will do this for you, or to make an alias to the cd command.
Some shells, such as Zsh and ksh offer a special form of the cd builtin:
cd [ -qsLP ] old new
The second form of cd substitutes the string new for the string old in the
name of the current directory, and tries to change to this new directory.
So if you are using zsh or ksh, then this command should do it:
cd /mystuff/myproject/tom /mystuff/myproject/richard
no matter which subdirectory of /mystuff/myproject/tom you happen to currently be in.

use bash $HOME in shell script

How to made bash to execute variable value.
For example, we have this code, where variable value was set in single quotes(!).
#!/bin/bash
V_MY_PATH='$HOME'
echo "$V_MY_PATH"
ls $V_MY_PATH
The output is
$HOME
ls: $HOME: No such file or directory
How to made bash to translate shell variable insto its value if there is some.
I want to add some code after V_MY_PATH='$HOME' to make output like echo $HOME.
It's something simple, but i'm stuck.
(NB: I know that with V_MY_PATH="$HOME", it works fine.)
EDIT PART:
I just wanted to make it simple, but I feel that some details are needed.
I'm getting parameter from a file. This part works good. I don't want to rewite it.
The problem is that when my V_MY_PATH contains a predefined variable (like $home) it's not treated like its value.
Remove the single quotes
V_MY_PATH='$HOME'
should be
V_MY_PATH=$HOME
you want to use $HOME as a variable
you can't have variables in single quotes.
Complete script:
#!/bin/bash
V_MY_PATH=$HOME
echo "$V_MY_PATH"
ls "$V_MY_PATH" #; Add double quotes here in case you get weird filenames
Output:
/home/myuser
0
05430142.pdf
4
aiSearchFramework-7_10_2007-v0.1.rar
etc.
use variable indirect reference so:
pete.mccabe#jackfrog$ p='HOME'
pete.mccabe#jackfrog$ echo $p
HOME
pete.mccabe#jackfrog$ ls ${p}
ls: cannot access HOME: No such file or directory
pete.mccabe#jackfrog$ ls ${!p}
bash libpng-1.2.44-1.el6 python-hwdata squid
...
pete.mccabe#jackfrog$
The ${!p} means take the value of $p and that value is the name of the variable who's contents I wish to reference
Use eval command:
#!/bin/bash
V_MY_PATH='$HOME'
echo "$V_MY_PATH"
eval ls $V_MY_PATH
You can use single or double quotes, and in your case, none if you prefer.
You have nothing telling bash what the variable is equal to. Maybe something like this? (unless I misunderstand what you are trying to do)
======================================================================
#!/bin/bash
#########################
# VARIABLES #
#########################
V_MY_PATH=home
#########################
# SCRIPT #
#########################
echo "What is your home path?"
read $home
echo "Your home path is $V_MY_PATH"
ls $V_MY_PATH
Of course you could also just remove the variable at the top and use:
echo "Your home path is $home"

Shell - Reading backslash in command line parameters

I'm thinking of writing a script for cygwin to cd into a windows directory which is copied from Windows explorer.
e.g.
cdw D:\working\test
equals to
cd /cygdrive/d/working/test
But it seems for shell script, all backslashs in parameters are ignored unless using single quote 'D:\working\test' or double backslashs D:\\working\\test.
But in my case it would be very inconvenience because I can't simply paste the directory name in the command line to execute the script.
Is there any way to make cdw D:\working\test working?
Well, you can do it, but you want something strange :)
cdw()
{
set $(history | tail -1 )
shift 2
path="$*"
cd $(cygpath "$path")
}
Example of usage:
$ cdw D:\working\test
$ pwd
/cygdrive/d/working/test
The main point here is the usage of history.
You don't use an argument directly, but get it from the history in the form it was typed.
$ rawarg() { set $(history | tail -1 ); shift 2; echo "$#"; }
$ rawarg C:\a\b\c\d
C:\a\b\c\d
Of course, you can use this trick in a interactive shell only (for obvious reasons).
The problem you deal with is related to the shell. Any argument you add to cdw on the command line, will be processed by the shell before cdw gets executed.
In order to prevent that processing to happen, you need at least one level of quoting,
either by enclosing the whole string in single quotes:
cd 'D:\working\test'
or with double backslashses:
cd D:\\working\test
A separate program will not help, because the damage is already done before it runs. ;-)
However, I have a possible function for cdw, which works in my AST UWIN ksh:
function cdw { typeset dir
read -r dir?"Paste Directory Path: "
cd ${dir:?}
}
And this one works in Bash (which does not support read var?prompt):
function cdw {
typeset dir
printf "Paste Directory Path: "
read -r dir || return
cd ${dir:?}
}
For me, I just type the two single quotes around the Pasted value.
The solution to add single quotes allows to copy paste

Resources