Recursively override a rc file in bash - linux

This is similar to a .htaccess for directories.
I have following:
File: ~/.myapprc
APP_USER=alagu
APP_DOMAIN=goyaka.com
File: ~/testapp/.myapprc
APP_USER=alagu_test
APP_DOMAIN=localhost
What I want:
[alagu#~ ]$ echo $APP_USER
alagu
[alagu#~ ]$ cd ~/testapp
[alagu#~ ]$ echo $APP_USER
alagu_test
How do I get this done?

Looks like you want to source .myapprc whenever you change directory.
There's two avenues you could use that I can think of - PROMPT_COMMAND, and the DEBUG trap.
To do this with the first, you'd run the following once:
PROMPT_COMMAND="[ -f .myapprc ] && . .myapprc"
and with the second:
trap "[ -f .myapprc ] && . .myapprc" DEBUG
These will source the file once for every prompt, so if sourcing that file is expensive you could extend it to check if $PWD has changed.
You could also override cd, but this may break some shell scripts:
alias cd=cd_
function cd_
{
\cd "$#"
local ret=$?
[ -f .myapprc ] && . .myapprc
return $ret
}
But doing any of these really isn't a good idea - hey're all huge security holes since you'll end up running whatever commands are in .myapprc in whatever your current working dir is.
Late edit for Joachim - Use this with the PROMPT_COMMAND/trap solutions can avoid excessive execution of .myapprc with the following:
PROMPT_COMMAND='if [ -f .myapprc -a "$PWD" != "$PWDLAST" ]; then PWDLAST="$PWD"; source .myapprc; fi'

You can create a function in your .bashrc that overrides the cd command:
cd() {
# "$#" to preserve quoting/whitespace
builtin cd "$#"
[ -f ".myapprc" ] && source .myapprc
}

You can customize your environment based on your working directory with direnv. It's at http://direnv.net .

Related

linux cd to folder containing the file

Having a file path I would like to go to the directory of the file. A full path is like this:
/Users/user1/Documents/workspace/project/src/file.py
I would like in terminal to go the folder containing this file.
In this case it would be:
/Users/user1/Documents/workspace/project/src/
Right now I have to paste the file in terminal, delete the file name and do cd. Like:
cd /Users/user1/Documents/workspace/project/src/
Is there a command/ any way I can skip deleting the file and use a smart command to know it should go to the directory containing this file?
I have to examine daily a lot of files and deleting the filename from path everytime is becoming a very tedious task.
The desired action would be something like this:
>>smart_cd /path/to/file.py
>>pwd
/path/to
Assuming your are using Bash: You can put in your ~/.bashrc
function smart_cd() {
cd "$(dirname $1)"
}
then: (after re-loading your .bashrc)
$ smart_cd /Users/user1/Documents/workspace/project/src/file.py
Bonus: you can also replace your cd command to handle both normal and smart cd:
function cd() {
if [ $# -eq 0 ] ; then
# no arguments
builtin cd
elif [ -d $1 ] ; then
# argument is a directory
builtin cd "$1"
else
# argument is not a directory
builtin cd "$(dirname $1)"
fi
}
For more information see:
Special Parameters in man bash for $#
man test for explanation about [ ... -eq ... ] and [ -d ... ]

linux zip and exclude dir via bash/shell script

I am trying to write a bash/shell script to zip up a specific folder and ignore certain sub-dirs in that folder.
This is the folder I am trying to zip "sync_test5":
My bash script generates an ignore list (based on) and calls the zip function like this:
#!/bin/bash
SYNC_WEB_ROOT_BASE_DIR="/home/www-data/public_html"
SYNC_WEB_ROOT_BACKUP_DIR="sync_test5"
SYNC_WEB_ROOT_IGNORE_DIR="dir_to_ignore dir2_to_ignore"
ignorelist=""
if [ "$SYNC_WEB_ROOT_IGNORE_DIR" != "" ];
then
for ignoredir in $SYNC_WEB_ROOT_IGNORE_DIR
do
ignorelist="$ignorelist $SYNC_WEB_ROOT_BACKUP_DIR/$ignoredir/**\*"
done
fi
FILE="$SYNC_BACKUP_DIR/$DATETIMENOW.website.zip"
cd $SYNC_WEB_ROOT_BASE_DIR;
zip -r $FILE $SYNC_WEB_ROOT_BACKUP_DIR -x $ignorelist >/dev/null
echo "Done"
Now this script runs without error, however it is not ignoring/excluding the dirs I've specified.
So, I had the shell script output the command it tried to run, which was:
zip -r 12-08-2014_072810.website.zip sync_test5 -x sync_test5/dir_to_ignore/**\* sync_test5/dir2_to_ignore/**\*
Now If I run the above command directly in putty like this, it works:
So, why doesn't my shell script exclude working as intended? the command that is being executed is identical (in shell and putty directly).
Because backslash quotings in a variable after word splitting are not evaluated.
If you have a='123\4', echo $a would give
123\4
But if you do it directly like echo 123\4, you'd get
1234
Clearly the arguments you pass with the variable and without the variables are different.
You probably just meant to not quote your argument with backslash:
ignorelist="$ignorelist $SYNC_WEB_ROOT_BACKUP_DIR/$ignoredir/***"
Btw, what actual works is a non-evaluated glob pattern:
zip -r 12-08-2014_072810.website.zip sync_test5 -x 'sync_test5/dir_to_ignore/***' 'sync_test5/dir2_to_ignore/***'
You can verify this with
echo zip -r 12-08-2014_072810.website.zip sync_test5 -x sync_test5/dir_to_ignore/**\* sync_test5/dir2_to_ignore/**\*
And this is my suggestion:
#!/bin/bash
SYNC_WEB_ROOT_BASE_DIR="/home/www-data/public_html"
SYNC_WEB_ROOT_BACKUP_DIR="sync_test5"
SYNC_WEB_ROOT_IGNORE_DIR=("dir_to_ignore" "dir2_to_ignore")
IGNORE_LIST=()
if [[ -n $SYNC_WEB_ROOT_IGNORE_DIR ]]; then
for IGNORE_DIR in "${SYNC_WEB_ROOT_IGNORE_DIR[#]}"; do
IGNORE_LIST+=("$SYNC_WEB_ROOT_BACKUP_DIR/$IGNORE_DIR/***") ## "$SYNC_WEB_ROOT_BACKUP_DIR/$IGNORE_DIR/*" perhaps is enough?
done
fi
FILE="$SYNC_BACKUP_DIR/$DATETIMENOW.website.zip" ## Where is $SYNC_BACKUP_DIR set?
cd "$SYNC_WEB_ROOT_BASE_DIR";
zip -r "$FILE" "$SYNC_WEB_ROOT_BACKUP_DIR" -x "${IGNORE_LIST[#]}" >/dev/null
echo "Done"
This is what I ended up with:
#!/bin/bash
# This script zips a directory, excluding specified files, types and subdirectories.
# while zipping the directory it excludes hidden directories and certain file types
[[ "`/usr/bin/tty`" == "not a tty" ]] && . ~/.bash_profile
DIRECTORY=$(cd `dirname $0` && pwd)
if [[ -z $1 ]]; then
echo "Usage: managed_directory_compressor /your-directory/ zip-file-name"
else
DIRECTORY_TO_COMPRESS=${1%/}
ZIPPED_FILE="$2.zip"
COMPRESS_IGNORE_FILE=("\.git" "*.zip" "*.csv" "*.json" "gulpfile.js" "*.rb" "*.bak" "*.swp" "*.back" "*.merge" "*.txt" "*.sh" "bower_components" "node_modules")
COMPRESS_IGNORE_DIR=("bower_components" "node_modules")
IGNORE_LIST=("*/\.*" "\.* "\/\.*"")
if [[ -n $COMPRESS_IGNORE_FILE ]]; then
for IGNORE_FILES in "${COMPRESS_IGNORE_FILE[#]}"; do
IGNORE_LIST+=("$DIRECTORY_TO_COMPRESS/$IGNORE_FILES/*")
done
for IGNORE_DIR in "${COMPRESS_IGNORE_DIR[#]}"; do
IGNORE_LIST+=("$DIRECTORY_TO_COMPRESS/$IGNORE_DIR/")
done
fi
zip -r "$ZIPPED_FILE" "$DIRECTORY_TO_COMPRESS" -x "${IGNORE_LIST[#]}" # >/dev/null
# echo zip -r "$ZIPPED_FILE" "$DIRECTORY_TO_COMPRESS" -x "${IGNORE_LIST[#]}" # >/dev/null
echo $DIRECTORY_TO_COMPRESS "compressed as" $ZIPPED_FILE.
fi
After a few trial and error, I have managed to fix this problem by changing this line:
ignorelist="$ignorelist $SYNC_WEB_ROOT_BACKUP_DIR/$ignoredir/**\*"
to:
ignorelist="$ignorelist $SYNC_WEB_ROOT_BACKUP_DIR/$ignoredir/***"
Not sure why this worked, but it does :)

How to make a file and its parent directory at once? [duplicate]

I know you can do mkdir to create a directory and touch to create a file, but is there no way to do both operations in one go?
i.e. if I want to do the below when the folder other does not exist:
cp /my/long/path/here/thing.txt /my/other/path/here/cpedthing.txt
Error:
cp: cannot create regular file `/my/other/path/here/cpedthing.txt': No such file or directory
Has anyone come up with a function as a workaround for this?
Use && to combine two commands in one shell line:
COMMAND1 && COMMAND2
mkdir -p /my/other/path/here/ && touch /my/other/path/here/cpedthing.txt
Note: Previously I recommended usage of ; to separate the two commands but as pointed out by #trysis it's probably better to use && in most situations because in case COMMAND1 fails COMMAND2 won't be executed either. (Otherwise this might lead to issues you might not have been expecting.)
You need to make all of the parent directories first.
FILE=./base/data/sounds/effects/camera_click.ogg
mkdir -p "$(dirname "$FILE")" && touch "$FILE"
If you want to get creative, you can make a function:
mktouch() {
if [ $# -lt 1 ]; then
echo "Missing argument";
return 1;
fi
for f in "$#"; do
mkdir -p -- "$(dirname -- "$f")"
touch -- "$f"
done
}
And then use it like any other command:
mktouch ./base/data/sounds/effects/camera_click.ogg ./some/other/file
Do it with /usr/bin/install:
install -D /my/long/path/here/thing.txt /my/other/path/here/cpedthing.txt
when you don't have a source file:
install -D <(echo 1) /my/other/path/here/cpedthing.txt
This is what I would do:
mkdir -p /my/other/path/here && touch $_/cpredthing.txt
Here, the $_ is a variable that represents the last argument to the previous command that we executed in line.
As always if you want to see what the output might be, you can test it by using the echo command, like so:
echo mkdir -p /code/temp/other/path/here && echo touch $_/cpredthing.txt
Which outputs as:
mkdir -p /code/temp/other/path/here
touch /code/temp/other/path/here/cpredthing.txt
As a bonus, you could write multiple files at once using brace expansion, for example:
mkdir -p /code/temp/other/path/here &&
touch $_/{cpredthing.txt,anotherfile,somescript.sh}
Again, totally testable with echo:
mkdir -p /code/temp/other/path/here
touch /code/temp/other/path/here/cpredthing.txt /code/temp/other/path/here/anotherfile /code/temp/other/path/here/somescript.sh
#!/bin/sh
for f in "$#"; do mkdir -p "$(dirname "$f")"; done
touch "$#"
you can do it in two steps:
mkdir -p /my/other/path/here/
touch /my/other/path/here/cpedthing.txt
as I saw and test in a unix forum this solves the problem
ptouch() {
for p in "$#"; do
_dir="$(dirname -- "$p")"
[ -d "$_dir" ] || mkdir -p -- "$_dir"
touch -- "$p"
done
}
if [ ! -d /my/other ]
then
mkdir /my/other/path/here
cp /my/long/path/here/thing.txt /my/other/path/here/cpedthing.txt
fi
no need for if then statements...
you can do it on a single line usign ;
mkdir -p /my/other/path/here;cp /my/long/path/here/thing.txt /my/other/path/here/cpedthing.txt
-- or on two lines --
mkdir -p /my/other/path/here
cp /my/long/path/here/thing.txt /my/other/path/here/cpedthing.txt
-- the -p prevents error returns if the directory already exists (which is what I came here looking for :))
In the special (but not uncommon) case where you are trying to recreate the same directory hierarchy, cp --parents can be useful.
For example if /my/long contains the source files, and my/other already exists, you can do this:
cd /my/long
cp --parents path/here/thing.txt /my/other
if you want simple with only 1 param snippet :
rm -rf /abs/path/to/file; #prevent cases when old file was a folder
mkdir -p /abs/path/to/file; #make it fist as a dir
rm -rf /abs/path/to/file; #remove the leaf of the dir preserving parents
touch /abs/path/to/file; #create the actual file

How do I get the absolute directory of a file in Bash?

I have written a Bash script that takes an input file as an argument and reads it.
This file contains some paths (relative to its location) to other files.
I would like the script to go to the folder containing the input file, to execute further commands.
In Linux, how do I get the folder (and just the folder) from an input file?
To get the full path use:
readlink -f relative/path/to/file
To get the directory of a file:
dirname relative/path/to/file
You can also combine the two:
dirname $(readlink -f relative/path/to/file)
If readlink -f is not available on your system you can use this*:
function myreadlink() {
(
cd "$(dirname $1)" # or cd "${1%/*}"
echo "$PWD/$(basename $1)" # or echo "$PWD/${1##*/}"
)
}
Note that if you only need to move to a directory of a file specified as a relative path, you don't need to know the absolute path, a relative path is perfectly legal, so just use:
cd $(dirname relative/path/to/file)
if you wish to go back (while the script is running) to the original path, use pushd instead of cd, and popd when you are done.
* While myreadlink above is good enough in the context of this question, it has some limitation relative to the readlink tool suggested above. For example it doesn't correctly follow a link to a file with different basename.
Take a look at realpath which is available on GNU/Linux, FreeBSD and NetBSD, but not OpenBSD 6.8. I use something like:
CONTAININGDIR=$(realpath ${FILEPATH%/*})
to do what it sounds like you're trying to do.
This will work for both file and folder:
absPath(){
if [[ -d "$1" ]]; then
cd "$1"
echo "$(pwd -P)"
else
cd "$(dirname "$1")"
echo "$(pwd -P)/$(basename "$1")"
fi
}
$cat abs.sh
#!/bin/bash
echo "$(cd "$(dirname "$1")"; pwd -P)"
Some explanations:
This script get relative path as argument "$1"
Then we get dirname part of that path (you can pass either dir or file to this script): dirname "$1"
Then we cd "$(dirname "$1"); into this relative dir
pwd -P and get absolute path. The -P option will avoid symlinks
As final step we echo it
Then run your script:
abs.sh your_file.txt
Try our new Bash library product realpath-lib over at GitHub that we have given to the community for free and unencumbered use. It's clean, simple and well documented so it's great to learn from. You can do:
get_realpath <absolute|relative|symlink|local file path>
This function is the core of the library:
if [[ -f "$1" ]]
then
# file *must* exist
if cd "$(echo "${1%/*}")" &>/dev/null
then
# file *may* not be local
# exception is ./file.ext
# try 'cd .; cd -;' *works!*
local tmppwd="$PWD"
cd - &>/dev/null
else
# file *must* be local
local tmppwd="$PWD"
fi
else
# file *cannot* exist
return 1 # failure
fi
# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success
}
It's Bash 4+, does not require any dependencies and also provides get_dirname, get_filename, get_stemname and validate_path.
Problem with the above answer comes with files input with "./" like "./my-file.txt"
Workaround (of many):
myfile="./somefile.txt"
FOLDER="$(dirname $(readlink -f "${ARG}"))"
echo ${FOLDER}
I have been using readlink -f works on linux
so
FULL_PATH=$(readlink -f filename)
DIR=$(dirname $FULL_PATH)
PWD=$(pwd)
cd $DIR
#<do more work>
cd $PWD

Unix - create path of folders and file

I know you can do mkdir to create a directory and touch to create a file, but is there no way to do both operations in one go?
i.e. if I want to do the below when the folder other does not exist:
cp /my/long/path/here/thing.txt /my/other/path/here/cpedthing.txt
Error:
cp: cannot create regular file `/my/other/path/here/cpedthing.txt': No such file or directory
Has anyone come up with a function as a workaround for this?
Use && to combine two commands in one shell line:
COMMAND1 && COMMAND2
mkdir -p /my/other/path/here/ && touch /my/other/path/here/cpedthing.txt
Note: Previously I recommended usage of ; to separate the two commands but as pointed out by #trysis it's probably better to use && in most situations because in case COMMAND1 fails COMMAND2 won't be executed either. (Otherwise this might lead to issues you might not have been expecting.)
You need to make all of the parent directories first.
FILE=./base/data/sounds/effects/camera_click.ogg
mkdir -p "$(dirname "$FILE")" && touch "$FILE"
If you want to get creative, you can make a function:
mktouch() {
if [ $# -lt 1 ]; then
echo "Missing argument";
return 1;
fi
for f in "$#"; do
mkdir -p -- "$(dirname -- "$f")"
touch -- "$f"
done
}
And then use it like any other command:
mktouch ./base/data/sounds/effects/camera_click.ogg ./some/other/file
Do it with /usr/bin/install:
install -D /my/long/path/here/thing.txt /my/other/path/here/cpedthing.txt
when you don't have a source file:
install -D <(echo 1) /my/other/path/here/cpedthing.txt
This is what I would do:
mkdir -p /my/other/path/here && touch $_/cpredthing.txt
Here, the $_ is a variable that represents the last argument to the previous command that we executed in line.
As always if you want to see what the output might be, you can test it by using the echo command, like so:
echo mkdir -p /code/temp/other/path/here && echo touch $_/cpredthing.txt
Which outputs as:
mkdir -p /code/temp/other/path/here
touch /code/temp/other/path/here/cpredthing.txt
As a bonus, you could write multiple files at once using brace expansion, for example:
mkdir -p /code/temp/other/path/here &&
touch $_/{cpredthing.txt,anotherfile,somescript.sh}
Again, totally testable with echo:
mkdir -p /code/temp/other/path/here
touch /code/temp/other/path/here/cpredthing.txt /code/temp/other/path/here/anotherfile /code/temp/other/path/here/somescript.sh
#!/bin/sh
for f in "$#"; do mkdir -p "$(dirname "$f")"; done
touch "$#"
you can do it in two steps:
mkdir -p /my/other/path/here/
touch /my/other/path/here/cpedthing.txt
as I saw and test in a unix forum this solves the problem
ptouch() {
for p in "$#"; do
_dir="$(dirname -- "$p")"
[ -d "$_dir" ] || mkdir -p -- "$_dir"
touch -- "$p"
done
}
if [ ! -d /my/other ]
then
mkdir /my/other/path/here
cp /my/long/path/here/thing.txt /my/other/path/here/cpedthing.txt
fi
no need for if then statements...
you can do it on a single line usign ;
mkdir -p /my/other/path/here;cp /my/long/path/here/thing.txt /my/other/path/here/cpedthing.txt
-- or on two lines --
mkdir -p /my/other/path/here
cp /my/long/path/here/thing.txt /my/other/path/here/cpedthing.txt
-- the -p prevents error returns if the directory already exists (which is what I came here looking for :))
In the special (but not uncommon) case where you are trying to recreate the same directory hierarchy, cp --parents can be useful.
For example if /my/long contains the source files, and my/other already exists, you can do this:
cd /my/long
cp --parents path/here/thing.txt /my/other
if you want simple with only 1 param snippet :
rm -rf /abs/path/to/file; #prevent cases when old file was a folder
mkdir -p /abs/path/to/file; #make it fist as a dir
rm -rf /abs/path/to/file; #remove the leaf of the dir preserving parents
touch /abs/path/to/file; #create the actual file

Resources