bash save last user input value permanently in the script itself - bash

Is it possible to save last entered value of a variable by the user in the bash script itself so that I reuse value the next time while executing again?.
Eg:
#!/bin/bash
if [ -d "/opt/test" ]; then
echo "Enter path:"
read path
p=$path
else
.....
........
fi
The above script is just a sample example I wanted to give(which may be wrong), is it possible if I want to save the value of p permanently in the script itself to so that I use it somewhere later in the script even when the script is re-executed?.
EDIT:
I am already using sed to overwrite the lines in the script while executing, this method works but this is not at all good practice as said. Replacing the lines in the same file as said in the below answer is much better than what I am using like the one below:
...
....
PATH=""; #This is line no 7
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )";
name="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")";
...
if [ condition ]
fi
path=$path
sed -i '7s|.*|PATH='$path';|' $DIR/$name;

Someting like this should do the asked stuff :
#!/bin/bash
ENTERED_PATH=""
if [ "$ENTERED_PATH" = "" ]; then
echo "Enter path"
read path
ENTERED_PATH=$path
sed -i 's/ENTERED_PATH=""/ENTERED_PATH='$path'/g' $0
fi
This script will ask user a path only if not previously ENTERED_PATH were defined, and store it directly into the current file with the sed line.
Maybe a safer way to do this, would be to write a config file somewhere with the data you want to save and source it . data.saved at the begining of your script.

In the script itself? Yes with sed but it's not advisable.
#!/bin/bash
test='0'
echo "test currently is: $test";
test=`expr $test + 1`
echo "changing test to: $test"
sed -i "s/test='[0-9]*'/test='$test'/" $0
Preferable method:
Try saving the value in a seperate file you can easily do a
myvar=`cat varfile.txt`
And whatever was in the file is not in your variable.
I would suggest using the /tmp/ dir to store the file in.

Another option would be to save the value as an extended attribute attached to the script file. This has many of the same problems as editing the script's contents (permissions issues, weird for multiple users, etc) plus a few of its own (not supported on all filesystems...), but IMHO it's not quite as ugly as rewriting the script itself (a config file really is a better option).
I don't use Linux, but I think the relevant commands would be something like this:
path="$(getfattr --only-values -n "user.saved_path" "${BASH_SOURCE[0]}")"
if [[ -z "$path" ]]; then
read -p "Enter path:" path
setfattr -n "user.saved_path" -v "$path" "${BASH_SOURCE[0]}"
fi

Related

How to write a while loop where I'm using -d option in test condition?

How do I test a directory in a while loop where if the directory doesn't exist, then it does the following blank things. I'm trying to prompt the user for input when the directory doesn't exist so the user can retry. Once they get it right, then the script exits.
My script has an infinite loop and I don't know how to fix it. Here is what my bash script looks like:
#!/bin/bash
source_dir=$1
dest_dir=$2
while [[ ! -d "$source_dir" ]]
do
echo "This is not a directory. Enter the source directory: "
read "$source_dir"
done
while [[ ! -d "$dest_dir" ]]
do
echo "This is not a directory. enter the destination directory: "
read "$dest_dir"
done
Certainly,
read "$source_dir"
does not make sense. To understand this, assume that your script is called with the parameter FOO. Hence, you first set source_dir to FOO, and the test [[ -d $source_dir ]] will do a [[ -d FOO ]] and the directory FOO does not exist. Therefore you perform your read command, which will parameter-expand into read FOO. This means to read one line from STDIN and store it into the variable FOO.
If you want to change the value of the variable source_dir, you have to do a
read source_dir

Change directory for saving a file and return to old directory

I wrote a very little and basic script which compares two files and writes all matching lines into a file.
I now want to secure, that no matter from which directory/working directory you run the bash script, the file is stored in the directory where the script is located.
#! /bin/bash
typeset -i count=1
typeset -i useable_counter=1
file="Fundstellen.txt"
curDir=`pwd`
wantedDir="/Users/Stephan/Documents/Schule/SYT/Skripting/bin/Uebungen"
echo `pushd ${wantedDir}`
if [ -e $file ]; then
echo `chmod 777 ${file}`
echo `rm ${file}`
fi
echo `touch ${file}`
while read pass; do
pass_nr=`echo $pass | cut -d ":" -f 3`
while read groups; do
group_nr=`echo $groups | cut -d ":" -f 3`
if [ "$pass_nr" = "$group_nr" ]; then
if [ $count -gt 15 ]; then
echo "#$useable_counter: $pass in $groups" >> $file
useable_counter=$useable_counter+1
fi
count=$count+1
fi
done < /etc/group
done < /etc/passwd
echo `chmod 444 $file`
echo `popd`
echo "Writing done!"
That's my script with the pushd command to get to the directory in which the script is located and popd should return.
But still, the output file is created in the directory/working directory, from where the script is launched.
What do I have to change so it'll work? I already tried to use normal cd to change the directory, that's why the variable curDir stores the starting directory.
By putting pushd into backticks, you're running it in a subshell. No subshell can change the current working directory of its parent shell.
Just call
pushd "$wantedDir"
directly, and same with popd.
Your script is a hopeless mess. All you need to produce output in the named file is
command >/Users/Stephan/Documents/Schule/SYT/Skripting/bin/Uebungen/Fundstellen.txt
Generally, very few scripts need to explicitly cd and fewer still would need to pushd and popd -- these commands are almost exclusively for interactive use.
The loop where you read all of the group file for every entry in the passwd file is extremely inefficient, especially when the purpose of the inner loop seems to be to find only a small subset of the records in the file. Very often, when you see while read, you want Awk instead. Here is a simple framework for doing that.
awk -F : 'NR==FNR { ++p[$3]; next }
FNR > 15 && $3 in p { print "#" ++i ": " $3 " in " $0 }' /etc/passdwd /etc/group
It's not clear what the 15 is supposed to accomplish. Is it a bug in your script, or is the intent to only skip the first 15 lines on the first iteration?

Bash function - return parent script file path

I have a bash script containing a function which is sourced by a number of different bash scripts. This function may fail based on its input, and I'd like to create logging within the function to identify what script(s) are causing failures.
E.g.,
source /path/to/function.sh
The closest I've come is this:
ps --no-heading -ocmd -p $$
This works well enough if the full file path is used to run the parent script, returning:
/bin/bash /path/to/parent.sh
But it fails to provide the full path if the parent script is run from a relative path, returning:
/bin/bash ./parent.sh
Ideally, I'd like a way to reliably return the parent script file path for both cases.
I suppose I could have each parent script pass its file path to the function (via $0 or similar), but that seems hard to enforce and not terribly elegant.
Any ideas, or alternative approaches? Should I not worry about the relative path case, and just use full/absolute file paths for everything?
Thanks!
I'm using Centos 5.9.
Bash version -
GNU bash, version 3.2.25(1)-release (x86_64-redhat-linux-gnu)
You can use readlink to follow all symbolic links to get an absolute path.
echo $(readlink -f $0)
As soon as the parent script starts export
"`pwd`/$0"
or so, into an env variable, say ORIG_SCRIPT, then in the function just use ORIG_SCRIPT.
You need to do this as soon as the script starts because $0 may be relative to the PWD and if you later change PWD before you need the value of ORIG_SCRIPT, it gets unnecessarily complicated.
Update:
Since you know the pid by $$, you may get something from /proc/<PID>/cmdline but I don't know how exactly this one works right now.
You could use ${BASH_SOURCE[1]} to get the script that calls the function but that is not always on absolute path form. You could get the absolute path of it by readlink -m, realpath, or other shell-script based solutions, but if your script changes directory from time to time, conversion of relative paths to absolute paths would no longer be accurate as those tools base from the current directory to get the actual form.
There's a workaround however but this requires that you won't change directories in your scripts before calling (sourcing) the script that contains the function. You would have to save the current directory in that script itself then base forming of absolute paths through that directory. You are free to change directories after the script has already been included. As an example:
ORIGINAL_PWD=$PWD
function x {
local CALLING_SCRIPT="${BASH_SOURCE[1]}"
if [[ -n $CALLING_SCRIPT ]]; then
if [[ $CALLING_SCRIPT == /* ]]; then
CALLING_SCRIPT=$(readlink -m "$CALLING_SCRIPT")
else
CALLING_SCRIPT=$(readlink -m "$ORIGINAL_PWD/$CALLING_SCRIPT")
fi
echo "Calling script: $CALLING_SCRIPT"
else
echo "Caller is not a script."
fi
}
Or
ORIGINAL_PWD=$PWD
function getabspath {
local -a T1 T2
local -i I=0
local IFS=/ A
case "$1" in
/*)
read -r -a T1 <<< "$1"
;;
*)
read -r -a T1 <<< "/$PWD/$1"
;;
esac
T2=()
for A in "${T1[#]}"; do
case "$A" in
..)
[[ I -ne 0 ]] && unset T2\[--I\]
continue
;;
.|'')
continue
;;
esac
T2[I++]=$A
done
case "$1" in
*/)
[[ I -ne 0 ]] && __="/${T2[*]}/" || __=/
;;
*)
[[ I -ne 0 ]] && __="/${T2[*]}" || __=/.
;;
esac
}
function x {
local CALLING_SCRIPT="${BASH_SOURCE[1]}"
if [[ -n $CALLING_SCRIPT ]]; then
if [[ $CALLING_SCRIPT == /* ]]; then
getabspath "$CALLING_SCRIPT"
else
getabspath "$ORIGINAL_PWD/$CALLING_SCRIPT"
fi
echo "Calling script: $__"
else
echo "Caller is not a script."
fi
}
You could also play around with FUNCNAME and BASH_LINENO to be more specific with the errors. I'm just not sure if they're already supported in Bash 3.2.
If you actually had Bash 4.0+ you could make use of associative arrays to map absolute paths with it but if there are two scripts with the same names or are called with almost similar names, one value could be overridden. There's no fix to that since we can't choose our keys from BASH_SOURCE.
Added Note: You could also prevent your script from being unnecessarily sourced multiple times as it only requires to be once through a solution like Shell Script Loader. You might find convenience through it as well.

bash - recursive script can't see files in sub directory

I got a recursive script which iterates a list of names, some of which are files and some are directories.
If it's a (non-empty) directory, I should call the script again with all of the files in the directory and check if they are legal.
The part of the code making the recursive call:
if [[ -d $var ]] ; then
if [ "$(ls -A $var)" ]; then
./validate `ls $var`
fi
fi
The part of code checking if the files are legal:
if [[ -f $var ]]; then
some code
fi
But, after making the recursive calls, I can no longer check any of the files inside that directory, because they are not in the same directory as the main script, the -f $var if cannot see them.
Any suggestion how can I still see them and use them?
Why not use find? Simple and easy solution to the problem.
Always quote variables, you never known when you will find a file or directory name with spaces
shopt -s nullglob
if [[ -d "$path" ]] ; then
contents=( "$path"/* )
if (( ${#contents[#]} > 0 )); then
"$0" "${contents[#]}"
fi
fi
you're re-inventing find
of course, var is a lousy variable name
if you're recursively calling the script, you don't need to hard-code the script name.
you should consider putting the logic into a function in the script, and the function can recursively call itself, instead of having to spawn an new process to invoke the shell script each time. If you do this, use $FUNCNAME instead of "$0"
A few people have mentioned how find might solve this problem, I just wanted to show how that might be done:
find /yourdirectory -type f -exec ./validate {} +;
This will find all regular files in yourdirectory and recursively in all its sub-directories, and return their paths as arguments to ./validate. The {} is expanded to the paths of the files that find locates within yourdirectory. The + at the end means that each call to validate will be on a large number of files, instead of calling it individually on each file (wherein the + is replaced with a \), this provides a huge speedup sometimes.
One option is to change directory (carefully) into the sub-directory:
if [[ -d "$var" ]] ; then
if [ "$(ls -A $var)" ]; then
(cd "$var"; exec ./validate $(ls))
fi
fi
The outer parentheses start a new shell so the cd command does not affect the main shell. The exec replaces the original shell with (a new copy of) the validate script. Using $(...) instead of back-ticks is sensible. In general, it is sensible to enclose variable names in double quotes when they refer to file names that might contain spaces (but see below). The $(ls) will list the files in the directory.
Heaven help you with the ls commands if any file names or directory names contain spaces; you should probably be using * glob expansion instead. Note that a directory containing a single file with a name such as -n would trigger a syntax error in your script.
Corrigendum
As Jens noted in a comment, the location of the shell script (validate) has to be adjusted as you descend the directory hierarchy. The simplest mechanism is to have the script on your PATH, so you can write exec validate or even exec $0 instead of exec ./validate. Failing that, you need to adjust the value of $0 — assuming your shell leaves $0 as a relative path and doesn't mess around with converting it to an absolute path. So, a revised version of the code fragment might be:
# For validate on PATH or absolute name in $0
if [[ -d "$var" ]] ; then
if [ "$(ls -A $var)" ]; then
(cd "$var"; exec $0 $(ls))
fi
fi
or:
# For validate not on PATH and relative name in $0
if [[ -d "$var" ]] ; then
if [ "$(ls -A $var)" ]; then
(cd "$var"; exec ../$0 $(ls))
fi
fi

Shell script to browse one or more directories passed as parameters

I made this script that should receive one or more parameter, and those parameter are all directories, and it has to browse those directories (one by one) and do some operations.
The operations work fine if the parameter is 1 (only one directory),
How should I modify my script to make it works if more than 1 parameter is passed
Example if I want it to do the same operations in 2 or 3 directories at the same time?
Thanks
#!/bin/sh
cd $1
for file in ./* # */
do
if [[ -d $file ]]
then
ext=dir
else
ext="${file##*.}"
fi
mv "${file}" "${file}.$ext"
done
First, if you are using bash use bash shebang (#! /bin/bash).
Then use
#! /bin/bash
for d in "$#"
do
echo "Do something with $d"
done
to iterate over the command line arguments (dirs in your case)
#!/bin/sh
for dir in "$#"; do
for file in "$dir"/*; do
echo "Doing something with '$file'"
done
done

Resources