For this problem I have two values, curdir and curlevel, which change throughout my script. I want to know if it's possible to create a variable and then use that value as the name for another value. For example
temp="dir_${curdir}_${curlevel}"
$temp=$name_of_directory **<----Is there a legitimate way to do this?**
so if initially curdir=1 and curlevel=0 then
$(temp)=directory_one
is equal to
dir_1_0=directory_one
then later if curdir=2 and curlevel=4, I can reset temp and then have
$(temp)=another_directory
is the same as
dir_2_4=another_directory
so I could make a call such as
cd $(temp)
which will move me to different directories when I need to
I think what you want is to use eval. Like so:
$ foo=bar
$ bar=baz
$ eval qux=\$$foo
$ echo $qux
baz
So what you could do is something like
eval temp=\$$"dir_${curdir}_${curlevel}"
cd $temp
The trick for this is to use eval - several times.
curdir=1
curlevel=0
temp='dir_${curdir}_${curlevel}' # Note single quotes!
x=$(eval echo $temp)
eval $x=$PWD
cd /tmp
curdir=2
curlevel=4
x=$(eval echo $temp)
eval $x=$PWD
echo $dir_1_0
echo $dir_2_4
The output of sh -x script:
+ curdir=1
+ curlevel=0
+ temp='dir_${curdir}_${curlevel}'
++ eval echo 'dir_${curdir}_${curlevel}'
+++ echo dir_1_0
+ x=dir_1_0
+ eval dir_1_0=/Users/jleffler/tmp/soq
++ dir_1_0=/Users/jleffler/tmp/soq
+ cd /tmp
+ curdir=2
+ curlevel=4
++ eval echo 'dir_${curdir}_${curlevel}'
+++ echo dir_2_4
+ x=dir_2_4
+ eval dir_2_4=/tmp
++ dir_2_4=/tmp
+ echo /Users/jleffler/tmp/soq
/Users/jleffler/tmp/soq
+ echo /tmp
/tmp
The output of sh script:
/Users/jleffler/tmp/soq
/tmp
Converted to a function:
change_dir()
{
temp='dir_${curdir}_${curlevel}' # Note single quotes!
x=$(eval echo $temp)
eval $x=$PWD
cd $1
}
curdir=1
curlevel=0
change_dir /tmp
curdir=2
curlevel=4
change_dir $HOME
echo $dir_1_0
echo $dir_2_4
pwd
Output:
/Users/jleffler/tmp/soq
/tmp
/Users/jleffler
The recorded names are the names of the directory being left, not the one you arrive at.
The secure way to do this is to use indirection, associative arrays (Bash 4), functions or declare:
Use declare:
declare $temp=$name_of_directory
Use indirection:
bar=42
foo=bar
echo ${!foo}
IFS= read -r $foo <<< 101
echo ${!foo}
Please take note of the security implications of eval.
Related
This question already has answers here:
What is the difference between $(command) and `command` in shell programming?
(6 answers)
Closed 3 years ago.
I do not understand why the output is the username because in line 3 and 4 must print /usr/bin/whoami.
please explantation simple to me
#!/bin/bash
WHEREWHOAMI="`which whoami`"
ROOTORNOT="`$WHEREWHOAMI`"
echo "$ROOTORNOT"
The variable ROOTORNOT is set to the output of the execution of WHEREWHOAMI which in turn is the output of the command which whoami.
WHEREWHOAMI=`which whoami` # <- /usr/bin/whoami
ROOTWHOAMI="`$WHEREWHOAMI`" # <- `/usr/bin/whoami` # <- username
You can easily figure out what is going on if you add the set -x flag to your script. Example:
$ set -x
$ WHEREWHOAMI="`which whoami`"
++ alias
++ declare -f
++ /usr/bin/which --tty-only --read-alias --read-functions --show-tilde --show-dot whoami
+ WHEREWHOAMI=/usr/bin/whoami
$ ROOTORNOT="`$WHEREWHOAMI`"
++ /usr/bin/whoami
+ ROOTORNOT=kvantour
$ echo "$ROOTORNOT"
+ echo kvantour
kvantour
$
Backticks are evaluated even inside double quotes.
(Suggestion - don't use backticks. use $() instead.)
WHEREWHOAMI="`which whoami`"
This executes which whoami and assigns /usr/bin/whoami to WHEREWHOAMI.
ROOTORNOT="`$WHEREWHOAMI`"
This executes /usr/bin/whoami in backticks, and assigns the USERNAME result to ROOTORNOT.
It's doing exactly what it should.
Is that not what you indended?
Perhaps what you wanted was something like -
$: [[ $( $(which whoami) ) == root ]] && echo ROOT || echo not-root
not-root
Though I do suggest storing the value and comparing that.
Is there a reason you can't just use
if [[ root == "$LOGNAME" ]]
then : ...
?
Im trying to cd into the md5 hash of whatever variable is set into the script but I do not get the correct value of md5, I think it has something to do with how I'm declaring my variables. Thank you for any help!
#!/bin/bash
var1=$1
md5=$(-n $var1 | md5sum)
cd /var/www/html/$md5
I expected it to take me to a directory given by the md5 hash:
$ ./myscript hello
(no output)
$ pwd
/var/www/html/5d41402abc4b2a76b9719d911017c592
Instead, it gives me errors and tries to cd to the wrong path:
$ ./myscript hello
./myscript: line 3: -n: command not found
./myscript: line 4: cd: /var/www/html/d41d8cd98f00b204e9800998ecf8427e: No such file or directory
$ pwd
/home/me
The md5sum it incorrectly tries to cd to is also the same no matter which value I input.
This works as a solution for anyone else having this issue
#!/bin/bash
md5=$*
hash="$(echo -n "$md5" | md5sum )"
cd /var/www/html/$hash
Your script:
#!/bin/bash
var1=$1
md5=$(-n $var1 | md5sum)
cd /var/www/html/$md5
This has a few issues:
-n is not a valid command in the pipeline -n $var1 | md5sum.
md5sum returns more than just the MD5 digest.
Changing the directory in a script will not be reflected in the calling shell.
Input is used unquoted.
I would write a shell function for this, rather than a script:
function md5cd {
dir="$( printf "%s" "$1" | md5sum - | cut -d ' ' -f 1 )"
cd /var/www/html/"$dir" || return 1
}
The function computes the MD5 digest of the given string using md5sum and cuts off the filename (-) that's part of the output. It then changes directory to the specified location. If the target directory does not exist, it signals this by returning a non-zero exit status.
Extending it to cd to a path constructed from the path on the command line, but with the last path element changed into a MD5 digest (just for fun):
function md5cd {
word="${1##*/}"
if [[ "$word" == "$1" ]]; then
prefix="."
else
prefix="${1%/*}"
fi
dir="$( cut -d ' ' -f 1 <( printf "%s" "$word" | md5sum - ) )"
cd "$prefix"/"$dir" || return 1
}
Testing it:
$ pwd
/home/myself
$ echo -n "hex this" | md5sum
990c0fc93296f9eed6651729c1c726d4 -
$ mkdir /tmp/990c0fc93296f9eed6651729c1c726d4
$ md5cd /tmp/"hex this"
$ pwd
/tmp/990c0fc93296f9eed6651729c1c726d4
Why do I get extra empty line when running 2). To me 1 is like 2. So why the extra line in 2)?
1)
export p1=$(cd $(dirname $0) && pwd)
# ^
echo p1
2)
export p2=$(cd $(dirname $0)) && pwd
# ^
echo p2
$echo $0
/bin/bash
$ echo $(cd $(dirname $0) && pwd)
/bin
$ echo $(cd $(dirname $0)) && pwd
/home/user
$
In the 1st expression it becomes echo $(cd /bin && pwd). Therefore the inner 2 commands execute in a subshell and return back the pwd value which is then echoed.
In the 2nd expression it gets reduced to echo $(cd /bin) && pwd. Therefore only the cd command executes in a subshell and returns nothing to echo (hence by default echo just prints an empty line). Since echo ran successfully(exit code=0) && results in true and pwd cmd is run in current shell and pwd gets printed
p1 captures the output of cd (empty) and pwd.
p2 only captures the output of cd, and then runs pwd without redirection.
echo p1 prints a literal p1 (with a newline). I guess you didn't actually copy-paste from your terminal, but instead typed in some thing else.
peter#tesla:~$ export p2=$(true) && pwd
/home/peter
peter#tesla:~$ echo "x${p2}x"
xx
cd in a subshell doesn't affect the parent shell's pwd, so I just substituted the true command to make it more readable.
I have found many snippets here and in other places that answer parts of this question. I have even managed to do this in many steps in an inefficient manner. If it is possible, I would really like to find single lines of execution that will perform this task, rather than having to assign to a variable and copy it a few times to perform the task.
e.g.
executeToVar ()
{
# Takes Arg1: NAME OF VARIABLE TO STORE IN
# All Remaining Arguments Are Executed
local STORE_INvar="${1}" ; shift
eval ${STORE_INvar}=\""$( "$#" 2>&1 )"\"
}
Overall does work, i.e. $ executeToVar SOME_VAR ls -l * # will actually fill SOME_VAR with the output of the execution of the ls -l * command that is taken from the rest of the arguments. However, if the command was to output empty lines at the end, (for e.g. - echo -e -n '\n\n123\n456\n789\n\n' which should have 2 x new lines at the start and the end ) these are stripped by bash's sub-execution process. I have seen in other posts similar to this that this has been solved by adding a token 'x' to the end of the stream, e.g. turning the sub-execution into something like:
eval ${STORE_INvar}=\""$( "$#" 2>&1 ; echo -n x )"\" # <-- ( Add echo -n x )
# and then if it wasn't an indirect reference to a var:
STORE_INvar=${STORE_INvar%x}
# However no matter how much I play with:
eval "${STORE_INvar}"=\""${STORE_INvar%x}"\"
# I am unable to indirectly remove the x from the end.
Anyway, I also need 2 x other variants on this, one that assigns the STDIN stream to the var and one that assigns the contents of a file to the var which I assume will be variations of this involving $( cat ${1} ), or maybe $( cat ${1:--} ) to give me a '-' if no filename. But, none of that will work until I can sort out the removal of the x that is needed to ensure accurate assignment of multi line variables.
I have also tried (but to no avail):
IFS='' read -d '' "${STORE_INvar}" <<<"$( $# ; echo -n x )"
eval \"'${STORE_INvar}=${!STORE_INvar%x}'\"
This is close to optimal -- but drop the eval.
executeToVar() { local varName=$1; shift; printf -v "$1" %s "$("$#")"; }
The one problem this formulation still has is that $() strips trailing newlines. If you want to prevent that, you need to add your own trailing character inside the subshell, and strip it off yourself.
executeToVar() {
local varName=$1; shift;
local val="$(printf %s x; "$#"; printf %s x)"; val=${val#x}
printf -v "$varName" %s "${val%x}"
}
If you want to read all content from stdin into a variable, this is particularly easy:
# This requires bash 4.1 for automatic fd allocation
readToVar() {
if [[ $2 && $2 != "-" ]]; then
exec {read_in_fd}<"$2" # copy from named file
else
exec {read_in_fd}<&0 # copy from stdin
fi
IFS= read -r -d '' "$1" <&$read_in_fd # read from the FD
exec {read_in_fd}<&- # close that FD
}
...used as:
readToVar var < <( : "run something here to read its output byte-for-byte" )
...or...
readToVar var filename
Testing these:
bash3-3.2$ executeToVar var printf '\n\n123\n456\n789\n\n'
bash3-3.2$ declare -p var
declare -- var="
123
456
789
"
...and...
bash4-4.3$ readToVar var2 < <(printf '\n\n123\n456\n789\n\n')
bash4-4.3$ declare -p var2
declare -- var2="
123
456
789
"
what'w wrong with storing in a file:
$ stuffToFile filename $(stuff)
where "stuffToFile" tests for a. > 1 argument, b. input on a pipe
$ ... commands ... | stuffToFile filename
and
$ stuffToFile filename < another_file
where "stoffToFile" is a function:
function stuffToFile
{
[[ -f $1 ]] || { echo $1 is not a file; return 1; }
[[ $# -lt 2 ]] && { cat - > $1; return; }
echo "$*" > $1
}
so, if "stuff" has leading and trailing blank lines, then you must:
$ stuff | stuffToFile filename
This function works:
source foo.bash && foo -n "a b c.txt"
The problem is, no matter what I've tried, I couldn't get the last line echo "$CMD" (or echo $CMD) to generate exactly this output:
cat -n "a b c.txt"
How to achieve that?
# foo.bash
function foo() {
local argv=("$#");
local OUT=`cat "${argv[#]}"`
local CMD=`echo cat "${argv[#]}"`
echo "--------------------------"
echo "$OUT"
echo "--------------------------"
echo "$CMD"
}
The output is instead:
cat -n a b c.txt
With this command: foo -n \"a b c.txt\" it does work for the display of the command, but it gives errors for the execution via the backtick.
The file "a b c.txt" is a valid, small, text file.
You need to escape quotes inside of the assignment:
local CMD="cat \"${argv[#]}\""
Also, echo is not needed to concatenate strings.
There you go, with the help of number of tokens in bash variable I've come up with the right solution.
I've almost forgot WHY we actually need quoting for one argument, it's because it has multiple words!
function foo() {
local argv=( "$#" );
local OUT=`cat "${argv[#]}"`
echo "--------------------------"
echo "$OUT"
echo "--------------------------"
local CMD="cat"
for word in "${argv[#]}"; do
words="${word//[^\ ]} "
if [[ ${#words} > 1 ]]; then
local CMD="$CMD \"${word}\""
else
local CMD="$CMD $word"
fi
done
echo "$CMD"
}
Hope it helps someone.