Unix Bash Alias Command - bash

I am trying to simplify my work with the help of Alias commands in my bash shell.
Problem Statement:
I want to copy different files from different directories to one single folder. The syntax i am using here is as below
cp <folder>/<file> <path>/file.dir
Here I want to save the destination file with filename.directory for easy identification. To achieve the same, I have written the below alias.
Alias Script
cp $Folder/$fileName ~/<path>/$fileName.$Folder
OR
cp $1/$2 ~/<path>/$2.$1
Expected output,
cp bin/file1 ~/Desktop/personal/file1.bin
cp etc/file2 ~/Desktop/personal/file2.etc*
However, It's failing at parsing the source file. i.e. $Folder is not replaced with my first argument.
cp: cannot stat `/file1': No such file or directory
I am writing the above script only to reduce my command lengths. As I am not expert in the above code, seeking any expert help in resolving the issue.

Rather than using an alias you could use a function which you define in some suitable location such as .profile or .bashrc
For example:
mycp()
{
folder=$1
filename=$2
if [ $# -ne 2 ]
then
echo "Two parameters not entered"
return
fi
if [ -d $folder -a -r $folder/$filename ]
then
cp $folder/$filename ~/playpen/$filename.$folder
else
echo "Invalid parameter"
fi
}

There is no way a bash alias can use arguments as you are trying to do. However, perl based rename can probably help you here. Note that it will effectively mv the files, not cp them.
rename 's|([^/]*)/(.*)|/home/user/path/$2.$1|' */*
Limitations: You can only process the files in 1 sub-directory level.
So, below alias can work (with above limitation):
$ alias backupfiles="rename 's|([^/]*)/(.*)|/home/user/path/\$2.\$1|'"
$ backupfiles */*
You can make more sophisticated perl expression if you want to work with multi-directory-level file structure.

A directory contains some files say ~/Documents/file1.d contains newfile.txt
joe#indiana:~/Documents$ ls -l $file
total 1
-rw-r--r-- 1 joe staff 0 May 5 11:39 newfile.txt
Add the variable 'file' in .bashrc for example my .bashrc is shown here
alias ll='ls -la'
file=~/Documents/file1.d
Now whenever you copy to '$file' it will copy to file1.d directory under ~/Documents :)

Related

How to source a file inside a symlinked script from the folder with the actual script file? [duplicate]

I want to translate this bash-script intro a zsh-script. Hence I have no experience with this I hope I may get help here:
bash script:
SCRIPT_PATH="${BASH_SOURCE[0]}";
if([ -h "${SCRIPT_PATH}" ]) then
while([ -h "${SCRIPT_PATH}" ]) do SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done
fi
pushd . > /dev/null
cd `dirname ${SCRIPT_PATH}` > /dev/null
SCRIPT_PATH=`pwd`;
popd > /dev/null
What I already know is that I can use
SCRIPT_PATH="$0"; to get the path were the script is located at. But then I get errors with the "readlink" statement.
Thanks for your help
Except for BASH_SOURCE I see no changes that you need to make. But what is the purpose of the script? If you want to get directory your script is located at there is ${0:A:h} (:A will resolve all symlinks, :h will truncate last path component leaving you with a directory name):
SCRIPT_PATH="${0:A:h}"
and that’s all. Note that original script has something strange going on:
if(…) and while(…) launch … in a subshell. You do not need subshell here, it is faster to do these checks using just if … and while ….
pushd . is not needed at all. While using pushd you normally replace the cd call with it:
pushd "$(dirname $SCRIPT_PATH)" >/dev/null
SCRIPT_PATH="$(pwd)"
popd >/dev/null
cd `…` will fail if … outputs something with spaces. It is possible for a directory to contain a space. In the above example I use "$(…)", "`…`" will also work.
You do not need trailing ; in variable declarations.
There is readlink -f that will resolve all symlinks thus you may consider reducing original script to SCRIPT_PATH="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))" (the behavior may change as your script seems to resolve symlinks only in last component): this is bash equivalent to ${0:A:h}.
if [ -h "$SCRIPT_PATH" ] is redundant since while body with the same condition will not be executed unless script path is a symlink.
readlink $SCRIPT_PATH will return symlink relative to the directory containing $SCRIPT_PATH. Thus original script cannot possibly used to resolve symlinks in last component.
There is no ; between if(…) and then. I am surprised bash accepts this.
All of the above statements apply both to bash and zsh.
If resolving only symlinks only in last component is essential you should write it like this:
SCRIPT_PATH="$0:a"
function ResolveLastComponent()
{
pushd "$1:h" >/dev/null
local R="$(readlink "$1")"
R="$R:a"
popd >/dev/null
echo $R
}
while test -h "$SCRIPT_PATH" ; do
SCRIPT_PATH="$(ResolveLastComponent "$SCRIPT_PATH")"
done
.
To illustrate 7th statement there is the following example:
Create directory $R/bash ($R is any directory, e.g. /tmp).
Put your script there without modifications, e.g. under name $R/bash/script_path.bash. Add line echo "$SCRIPT_PATH" at the end of it and line #!/bin/bash at the start for testing.
Make it executable: chmod +x $R/bash/script_path.bash.
Create a symlink to it: cd $R/bash && ln -s script_path.bash link.
cd $R
Launch $R/bash/1. Now you will see that your script outputs $R while it should output $R/bash like it does when you launch $R/bash/script_path.bash.

How to set a Directory as an Argument in Bash

I am having trouble finding out how to set a directory as an argument in bash.
The directory I am trying to have as an argument is /home/rrodriguez/Documents/one.
Anywhere I try to look for an answer I see examples like dir = $1 but I cant seem to find an explanation of what this means or how to set it up so that it references my specific file location. Could anyone show me how to set up my variable for my path directory?
Adding my code for a better understanding of what im trying to do:
#!bin/bash
$1 == 'home/rrodriguez/Documents/one/'
dir = $1
touch -c $dir/*
ls -la $dir
wc$dir/*
Consider:
#!bin/bash
dir=$1
touch -c "$dir"/*
ls -la "$dir"
This script takes one argument, a directory name, and touches files in that directory and then displays a directory listing. You can run it via:
bash script.sh 'home/rrodriguez/Documents/one/'
Since home/rrodriguez/Documents/one/ is the first argument to the script, it is assigned to $1 in the script.
Notes
In shell, never put spaces on either side of the = in an assignment.
I omitted the line wc$dir/* because it wasn't clear to me what the purpose of it was.
I put double-quotes around $dir to prevent the shell from, among other things, performing word-splitting. This would matter if dir contains spaces.

If-else-statement is working wrong in crontab?

When i use this:
*/5 6-18 * * 1-6 [ "$(ls -A /DIR_WHERE_FILES_ARE_OR_NOT/)" ] &&
rsync -au /DIR_WHERE_FILES_ARE_OR_NOT/ /DIR_WHERE_FILES_SHOLD_GO; \
mv /DIR_WHERE_FILES_ARE_OR_NOT/* /SAVE_DIR/ ||
mail -s "DIR IS EMPTY" myemail#klkldkl.de <<< "message"
i get two mails:
mv: cannot stat `/DIR_WHERE_FILES_ARE_OR_NOT/*': No such file or
directory
and
"DIR IS EMPTY"
Why?
You get
mv: cannot stat `/DIR_WHERE_FILES_ARE_OR_NOT/*': No such file or directory
for exactly the reason stated: that directory is empty, hence it does not contain a file named * (asterisk). It's just the way glob expansion works in the shell: if the glob doesn't match anything it is passed literally to the command. Since mv attemps to rename a non-existing file, it complains as shown.
This would all be much more readable, if instead of a sequence of && and || operators in a crontab you would place the whole logic in a script with equivalent if/else/fi constructs and just call the script from cron.
You get two mails because you explicitly send the first with mail -s. The second is from cron because the output on stderr and stdout is not empty.
Your commands are equivalent to
if [ "$(ls ...)" ]; then
rsync
fi
if ! mv; then
mail
fi
Note that there is no else.
Just like user Jens already mentioned, and also from my experience, unless you are using a very simple and usually single command, you should stick to script files. So, in your case, I would go with a script file. I'll give you an example.
#!/bin/bash
dir_where_files_are_or_not=/filespath
dir_where_files_should_go=/another/filespath
save_dir=/savefiles/path
# ok, lets start by checking if dir contains files
if [ "$(ls -A $dir_where_files_are_or_not)" ]; then
# dir contains files, so lets rsync and mv them
rsync -au $dir_where_files_are_or_not/ $dir_where_files_should_go
mv $dir_where_files_are_or_not/* $save_dir
else
# dir is empty, lets send email
mail -s "DIR IS EMPTY" myemail#klkldkl.de <<< "message"
fi
Now, I just put this code in a file. Give it a name, for example "chkfiles" and save it in a directory (I use /usr/local/sbin for all of my scripts).
Next, in a shell, run the command chmod +x /usr/local/sbin/chkfiles to make the file executable. Then add the script to your crontab.
I would suggest the following line inside crontab:
*/5 6-18 * * 1-6 /bin/bash /usr/local/sbin/chkfiles
I used /bin/bash to call the right interpreter for this script. It should now work as expected.
Important Notes:
Before running the script, you need to change the dir_where_files_are_or_not, dir_where_files_should_go and save_dir vars to your needs.
Do NOT include trailing slashes in the dirs, otherwise the rsync and mv might not do what you really want
Regards
You get two mails because when mv fails, cron captures what is written to standard error and mails it to the owner, then runs the mail command. You can suppress the error message from mv to avoid the mail from cron.
mv /DIR_WHERE_FILES_ARE_OR_NOT/* /SAVE_DIR/ 2> /dev/null || mail -s "DIR IS EMPTY" myemail#klkldkl.de <<< "message"

Bash shell: how to add a name

I am trying to rename some zip files in bash with an _orig but I seem to be missing something. Any suggestions??
My goal:
move files to an orig directory
rename original files with a "_orig" in the name
The code Ive tried to write:
mv -v $PICKUP/*.zip $ORIGINALS
for origfile in $(ls $ORIGINALS/*.zip);do
echo "Adding _orig to zip file"
echo
added=$(basename $origfile '_orig').zip
mv -v $ORIGINALS/$origfile.zip $ORIGINALS/$added.zip
done
Sorry still kinda new at this.
Using (p)rename :
cd <ZIP DIR>
mkdir -p orig
rename 's#(.*?)\.zip#orig/$1_orig.zip#' *.zip
rename is http://search.cpan.org/~pederst/rename/ (default on many distros)
Thanks to never use
for i in $(ls $ORIGINALS/*.zip);do
but use globs instead :
for i in $ORIGINALS/*.zip;do
See http://porkmail.org/era/unix/award.html#ls.
I know you've got a solution already, but just for posterity, this simplified version of your own shell script should also work for the case you seem to be describing:
mkdir -p "$ORIGINALS"
for file in "$PICKUP"/*.zip; do
mv -v "$file" "$ORIGINALS/${file%.zip}_orig.zip"
done
This makes use of "Parameter Expansion" in bash (you can look that up in bash's man page). The initial mkdir -p simply insures that the target directory exists. The quotes around $PICKUP and $ORIGINALS are intended to make it safe to include special characters like spaces and newlines in the directory names.
While prename is a powerful solution to many problems, it's certainly not the only hammer in the toolbox.

How best to include other scripts?

The way you would normally include a script is with "source"
eg:
main.sh:
#!/bin/bash
source incl.sh
echo "The main script"
incl.sh:
echo "The included script"
The output of executing "./main.sh" is:
The included script
The main script
... Now, if you attempt to execute that shell script from another location, it can't find the include unless it's in your path.
What's a good way to ensure that your script can find the include script, especially if for instance, the script needs to be portable?
I tend to make my scripts all be relative to one another.
That way I can use dirname:
#!/bin/sh
my_dir="$(dirname "$0")"
"$my_dir/other_script.sh"
I know I am late to the party, but this should work no matter how you start the script and uses builtins exclusively:
DIR="${BASH_SOURCE%/*}"
if [[ ! -d "$DIR" ]]; then DIR="$PWD"; fi
. "$DIR/incl.sh"
. "$DIR/main.sh"
. (dot) command is an alias to source, $PWD is the Path for the Working Directory, BASH_SOURCE is an array variable whose members are the source filenames, ${string%substring} strips shortest match of $substring from back of $string
An alternative to:
scriptPath=$(dirname $0)
is:
scriptPath=${0%/*}
.. the advantage being not having the dependence on dirname, which is not a built-in command (and not always available in emulators)
If it is in the same directory you can use dirname $0:
#!/bin/bash
source $(dirname $0)/incl.sh
echo "The main script"
I think the best way to do this is to use the Chris Boran's way, BUT you should compute MY_DIR this way:
#!/bin/sh
MY_DIR=$(dirname $(readlink -f $0))
$MY_DIR/other_script.sh
To quote the man pages for readlink:
readlink - display value of a symbolic link
...
-f, --canonicalize
canonicalize by following every symlink in every component of the given
name recursively; all but the last component must exist
I've never encountered a use case where MY_DIR is not correctly computed. If you access your script through a symlink in your $PATH it works.
A combination of the answers to this question provides the most robust solution.
It worked for us in production-grade scripts with great support of dependencies and directory structure:
#!/bin/bash
# Full path of the current script
THIS=`readlink -f "${BASH_SOURCE[0]}" 2>/dev/null||echo $0`
# The directory where current script resides
DIR=`dirname "${THIS}"`
# 'Dot' means 'source', i.e. 'include':
. "$DIR/compile.sh"
The method supports all of these:
Spaces in path
Links (via readlink)
${BASH_SOURCE[0]} is more robust than $0
SRC=$(cd $(dirname "$0"); pwd)
source "${SRC}/incl.sh"
1. Neatest
I explored almost every suggestion and here is the neatest one that worked for me:
script_root=$(dirname $(readlink -f $0))
It works even when the script is symlinked to a $PATH directory.
See it in action here: https://github.com/pendashteh/hcagent/blob/master/bin/hcagent
2. The coolest
# Copyright https://stackoverflow.com/a/13222994/257479
script_root=$(ls -l /proc/$$/fd | grep "255 ->" | sed -e 's/^.\+-> //')
This is actually from another answer on this very page, but I'm adding it to my answer too!
3. The most reliable
Alternatively, in the rare case that those didn't work, here is the bullet proof approach:
# Copyright http://stackoverflow.com/a/7400673/257479
myreadlink() { [ ! -h "$1" ] && echo "$1" || (local link="$(expr "$(command ls -ld -- "$1")" : '.*-> \(.*\)$')"; cd $(dirname $1); myreadlink "$link" | sed "s|^\([^/].*\)\$|$(dirname $1)/\1|"); }
whereis() { echo $1 | sed "s|^\([^/].*/.*\)|$(pwd)/\1|;s|^\([^/]*\)$|$(which -- $1)|;s|^$|$1|"; }
whereis_realpath() { local SCRIPT_PATH=$(whereis $1); myreadlink ${SCRIPT_PATH} | sed "s|^\([^/].*\)\$|$(dirname ${SCRIPT_PATH})/\1|"; }
script_root=$(dirname $(whereis_realpath "$0"))
You can see it in action in taskrunner source: https://github.com/pendashteh/taskrunner/blob/master/bin/taskrunner
Hope this help someone out there :)
Also, please leave it as a comment if one did not work for you and mention your operating system and emulator. Thanks!
This works even if the script is sourced:
source "$( dirname "${BASH_SOURCE[0]}" )/incl.sh"
You need to specify the location of the other scripts, there is no other way around it. I'd recommend a configurable variable at the top of your script:
#!/bin/bash
installpath=/where/your/scripts/are
. $installpath/incl.sh
echo "The main script"
Alternatively, you can insist that the user maintain an environment variable indicating where your program home is at, like PROG_HOME or somesuch. This can be supplied for the user automatically by creating a script with that information in /etc/profile.d/, which will be sourced every time a user logs in.
I'd suggest that you create a setenv script whose sole purpose is to provide locations for various components across your system.
All other scripts would then source this script so that all locations are common across all scripts using the setenv script.
This is very useful when running cronjobs. You get a minimal environment when running cron, but if you make all cron scripts first include the setenv script then you are able to control and synchronise the environment that you want the cronjobs to execute in.
We used such a technique on our build monkey that was used for continuous integration across a project of about 2,000 kSLOC.
Shell Script Loader is my solution for this.
It provides a function named include() that can be called many times in many scripts to refer a single script but will only load the script once. The function can accept complete paths or partial paths (script is searched in a search path). A similar function named load() is also provided that will load the scripts unconditionally.
It works for bash, ksh, pd ksh and zsh with optimized scripts for each one of them; and other shells that are generically compatible with the original sh like ash, dash, heirloom sh, etc., through a universal script that automatically optimizes its functions depending on the features the shell can provide.
[Fowarded example]
start.sh
This is an optional starter script. Placing the startup methods here is just a convenience and can be placed in the main script instead. This script is also not needed if the scripts are to be compiled.
#!/bin/sh
# load loader.sh
. loader.sh
# include directories to search path
loader_addpath /usr/lib/sh deps source
# load main script
load main.sh
main.sh
include a.sh
include b.sh
echo '---- main.sh ----'
# remove loader from shellspace since
# we no longer need it
loader_finish
# main procedures go from here
# ...
a.sh
include main.sh
include a.sh
include b.sh
echo '---- a.sh ----'
b.sh
include main.sh
include a.sh
include b.sh
echo '---- b.sh ----'
output:
---- b.sh ----
---- a.sh ----
---- main.sh ----
What's best is scripts based on it may also be compiled to form a single script with the available compiler.
Here's a project that uses it: http://sourceforge.net/p/playshell/code/ci/master/tree/. It can run portably with or without compiling the scripts. Compiling to produce a single script can also happen, and is helpful during installation.
I also created a simpler prototype for any conservative party that may want to have a brief idea of how an implementation script works: https://sourceforge.net/p/loader/code/ci/base/tree/loader-include-prototype.bash. It's small and anyone can just include the code in their main script if they want to if their code is intended to run with Bash 4.0 or newer, and it also doesn't use eval.
Steve's reply is definitely the correct technique but it should be refactored so that your installpath variable is in a separate environment script where all such declarations are made.
Then all scripts source that script and should installpath change, you only need to change it in one location. Makes things more, er, futureproof. God I hate that word! (-:
BTW You should really refer to the variable using ${installpath} when using it in the way shown in your example:
. ${installpath}/incl.sh
If the braces are left out, some shells will try and expand the variable "installpath/incl.sh"!
I put all my startup scripts in a .bashrc.d directory.
This is a common technique in such places as /etc/profile.d, etc.
while read file; do source "${file}"; done <<HERE
$(find ${HOME}/.bashrc.d -type f)
HERE
The problem with the solution using globbing...
for file in ${HOME}/.bashrc.d/*.sh; do source ${file};done
...is you might have a file list which is "too long".
An approach like...
find ${HOME}/.bashrc.d -type f | while read file; do source ${file}; done
...runs but doesn't change the environment as desired.
This should work reliably:
source_relative() {
local dir="${BASH_SOURCE%/*}"
[[ -z "$dir" ]] && dir="$PWD"
source "$dir/$1"
}
source_relative incl.sh
Using source or $0 will not give you the real path of your script. You could use the process id of the script to retrieve its real path
ls -l /proc/$$/fd |
grep "255 ->" |
sed -e 's/^.\+-> //'
I am using this script and it has always served me well :)
Of course, to each their own, but I think the block below is pretty solid. I believe this involves the "best" way to find a directory, and the "best" way to call another bash script:
scriptdir=`dirname "$BASH_SOURCE"`
source $scriptdir/incl.sh
echo "The main script"
So this may be the "best" way to include other scripts. This is based off another "best" answer that tells a bash script where it is stored
Personally put all libraries in a lib folder and use an import function to load them.
folder structure
script.sh contents
# Imports '.sh' files from 'lib' directory
function import()
{
local file="./lib/$1.sh"
local error="\e[31mError: \e[0mCannot find \e[1m$1\e[0m library at: \e[2m$file\e[0m"
if [ -f "$file" ]; then
source "$file"
if [ -z $IMPORTED ]; then
echo -e $error
exit 1
fi
else
echo -e $error
exit 1
fi
}
Note that this import function should be at the beginning of your script and then you can easily import your libraries like this:
import "utils"
import "requirements"
Add a single line at the top of each library (i.e. utils.sh):
IMPORTED="$BASH_SOURCE"
Now you have access to functions inside utils.sh and requirements.sh from script.sh
TODO: Write a linker to build a single sh file
we just need to find out the folder where our incl.sh and main.sh is stored; just change your main.sh with this:
main.sh
#!/bin/bash
SCRIPT_NAME=$(basename $0)
SCRIPT_DIR="$(echo $0| sed "s/$SCRIPT_NAME//g")"
source $SCRIPT_DIR/incl.sh
echo "The main script"
According man hier suitable place for script includes is /usr/local/lib/
/usr/local/lib
Files associated with locally installed programs.
Personally I prefer /usr/local/lib/bash/includes for includes.
There is bash-helper lib for including libs in that way:
#!/bin/bash
. /usr/local/lib/bash/includes/bash-helpers.sh
include api-client || exit 1 # include shared functions
include mysql-status/query-builder || exit 1 # include script functions
# include script functions with status message
include mysql-status/process-checker; status 'process-checker' $? || exit 1
include mysql-status/nonexists; status 'nonexists' $? || exit 1
Most of the answers I saw here seem to overcomplicate things. This method has always worked reliably for me:
FULLPATH=$(readlink -f $0)
INCPATH=${FULLPATH%/*}
INCPATH will hold the complete path of the script excluding the script filename, regardless of how the script is called (by $PATH, relative or absolute).
After that, one only needs to do this to include files in the same directory:
. $INCPATH/file_to_include.sh
Reference: TecPorto / Location independent includes
here is a nice function you can use. it builds on what #sacii made. thank you
it will let you list any number of space separated script names to source (relative to the script calling source_files).
optionally you can pass an absolute or relative path as the first argument and it will source from there instead.
you can call it multiple times (see example below) to source scripts from different dirs
#!/usr/bin/env bash
function source_files() {
local scripts_dir
scripts_dir="$1"
if [ -d "$scripts_dir" ]; then
shift
else
scripts_dir="${BASH_SOURCE%/*}"
if [[ ! -d "$scripts_dir" ]]; then scripts_dir="$PWD"; fi
fi
for script_name in "$#"; do
# shellcheck disable=SC1091 disable=SC1090
. "$scripts_dir/$script_name.sh"
done
}
here is an example you can run to show how its used
#!/usr/bin/env bash
function source_files() {
local scripts_dir
scripts_dir="$1"
if [ -d "$scripts_dir" ]; then
shift
else
scripts_dir="${BASH_SOURCE%/*}"
if [[ ! -d "$scripts_dir" ]]; then scripts_dir="$PWD"; fi
fi
for script_name in "$#"; do
# shellcheck disable=SC1091 disable=SC1090
. "$scripts_dir/$script_name.sh"
done
}
## -- EXAMPLE -- ##
# assumes dir structure:
# /
# source_files.sh
# sibling.sh
# scripts/
# child.sh
# nested/
# scripts/
# grandchild.sh
cd /tmp || exit 1
# sibling.sh
tee sibling.sh <<- EOF > /dev/null
#!/usr/bin/env bash
export SIBLING_VAR='sibling var value'
EOF
# scripts/child.sh
mkdir -p scripts
tee scripts/child.sh <<- EOF > /dev/null
#!/usr/bin/env bash
export CHILD_VAR='child var value'
EOF
# nested/scripts/grandchild.sh
mkdir -p nested/scripts
tee nested/scripts/grandchild.sh <<- EOF > /dev/null
#!/usr/bin/env bash
export GRANDCHILD_VAR='grandchild var value'
EOF
source_files 'sibling'
source_files 'scripts' 'child'
source_files 'nested/scripts' 'grandchild'
echo "$SIBLING_VAR"
echo "$CHILD_VAR"
echo "$GRANDCHILD_VAR"
rm sibling.sh
rm -rf scripts nested
cd - || exit 1
prints:
sibling var value
child var value
grandchild var value
You can also use:
PWD=$(pwd)
source "$PWD/inc.sh"

Resources