How to put array in command-string so that I can eval it? - linux

I've been struggling with this problem for a while. Let's assume I have two scripts.
test1.sh
test2.sh
The code in test1.sh is the following:
array1="/dir/file1.txt /dir/file2.txt /dir/file3.txt"
array2="/dir/file4.txt /dir/file5.txt /dir/file6.txt"
./test2.sh "$array1" "$array2"
The code in test2.sh is the following:
echo $1
echo $2
This works fine, and prints the two arrays correctly:
/dir/file1.txt /dir/file2.txt /dir/file3.txt
/dir/file4.txt /dir/file5.txt /dir/file6.txt
For the project I am working on I have to put the execution code in a variable so that I can run it with the eval-command. I've tried it as follows:
array1="/dir/file1.txt /dir/file2.txt /dir/file3.txt"
array2="/dir/file4.txt /dir/file5.txt /dir/file6.txt"
com="./test2.sh "$array1" "$array2" "
eval $com
However, this returns:
/dir/file1.txt
/dir/file2.txt
How do I get it to give the same input? I've been struggling with this for a while now and Im honestly pretty stuck. I believe it is caused by the many quatation marks in com-variable, but I am not sure.
Many thanks,
Patrick

Make com an array, and you don't need eval.
#!/usr/bin/env bash
# ^^^^- NOT /bin/sh. Run with "bash yourscript", not "sh yourscript"
# none of these are actually arrays; they're just misleadingly-named strings
array1="/dir/file1.txt /dir/file2.txt /dir/file3.txt"
array2="/dir/file4.txt /dir/file5.txt /dir/file6.txt"
# This is an actual array.
com=( ./test2.sh "$array1" "$array2" )
# Expand each element of the array into a separate word of a simple command
"${com[#]}"

Related

bin/bash nested loop does not work

I currently working as a intern at a hosting firm. They asked me to write a bin/bash script to help automate a process to check the user's domain's and .pointers for them. And validate with a "whois" command if the domains/pointers are on our server's.
I'm new with bin/bash scripting but i was told i should check nested loops out. So to test my script out i made similar paths as they would look like on the server. /usr/local/directadmin/data/users/#USER#/domains.list and users/#USER#/domains/#DOMAIN NAME OF USER#.pointers
#part 1
for i in $(cat /home/MrC/Desktop/Users) #<the list of users i need to check)
do
if [ -f "/usr/local/directadmin/data/users/$i/domainlist.txt" ]
then
echo "/usr/local/directadmin/data/users/$i" >> /home/MrC/Desktop/output.tx$
cat "/usr/local/directadmin/data/users/$i/domainlist.txt" >> /home/carlos/Des$
fi
#part 2
for s in $(cat /home/mrC/Desktop/output.txt)
do
if [ -f "/usr/local/directadmin/data/users/$i/domains/$s.pointers" ]
then
echo "/usr/local/directadmin/data/users/$i" >> /home/MrC/Desktop/pointers.$
cat "usr/local/directadmin/data/users/$i/domains/$s.pointers" >> /home/MrC$
fi
done
done
So part 1 works this is the output.txt below
/usr/local/directadmin/data/users/testuser
lolla.nl
blaat2.nl
blaat3.nl
google2.nl
/usr/local/directadmin/data/users/testusers
blaat.nl
google.com
test.nl
pietje.nl
But i cant seem part two to work (no pointer file). my goal with part two of the script is to read the output (domainname) and put it #/$i/domains/$s.pointers.
I'm new on the forum i hope i asked my question in a proper fashion. if some one could give me hints/tips to which direction i should look that would be highly appreciated.
For
Do
if
then
for
do
COMMAND A
COMMAND B
COMMAND C
done
fi
done
while read -r i; do #stuff; done < /home/MrC/Desktop/Users (adjust IFS or specify the delimiter with the -d option to read).
– David C. Rankin

How to store command arguments which contain double quotes in an array?

I have a Bash script which generates, stores and modifies values in an array. These values are later used as arguments for a command.
For a MCVE I thought of an arbitrary command bash -c 'echo 0="$0" ; echo 1="$1"' which explains my problem. I will call my command with two arguments -option1=withoutspace and -option2="with space". So it would look like this
> bash -c 'echo 0="$0" ; echo 1="$1"' -option1=withoutspace -option2="with space"
if the call to the command would be typed directly into the shell. It prints
0=-option1=withoutspace
1=-option2=with space
In my Bash script, the arguments are part of an array. However
#!/bin/bash
ARGUMENTS=()
ARGUMENTS+=('-option1=withoutspace')
ARGUMENTS+=('-option2="with space"')
bash -c 'echo 0="$0" ; echo 1="$1"' "${ARGUMENTS[#]}"
prints
0=-option1=withoutspace
1=-option2="with space"
which still shows the double quotes (because they are interpreted literally?). What works is
#!/bin/bash
ARGUMENTS=()
ARGUMENTS+=('-option1=withoutspace')
ARGUMENTS+=('-option2=with space')
bash -c 'echo 0="$0" ; echo 1="$1"' "${ARGUMENTS[#]}"
which prints again
0=-option1=withoutspace
1=-option2=with space
What do I have to change to make ARGUMENTS+=('-option2="with space"') work as well as ARGUMENTS+=('-option2=with space')?
(Maybe it's even entirely wrong to store arguments for a command in an array? I'm open for suggestions.)
Get rid of the single quotes. Write the options exactly as you would on the command line.
ARGUMENTS+=(-option1=withoutspace)
ARGUMENTS+=(-option2="with space")
Note that this is exactly equivalent to your second option:
ARGUMENTS+=('-option1=withoutspace')
ARGUMENTS+=('-option2=with space')
-option2="with space" and '-option2=with space' both evaluate to the same string. They're two ways of writing the same thing.
(Maybe it's even entirely wrong to store arguments for a command in an array? I'm open for suggestions.)
It's the exact right thing to do. Arrays are perfect for this. Using a flat string would be a mistake.

Using "read" to set variables

In bash from the CLI I can do:
$ ERR_TYPE=$"OVERLOAD"
$ echo $ERR_TYPE
OVERLOAD
$ read ${ERR_TYPE}_ERROR
1234
$ echo $OVERLOAD_ERROR
1234
This works great to set my variable name dynamically; in a script it doesn't work. I tried:
#!/bin/env bash
ERR_TYPE=("${ERR_TYPE[#]}" "OVERLOAD" "PANIC" "FATAL")
for i in "${ERR_TYPE[#]}"
do
sh -c $(echo ${i}_ERROR=$"1234")
done
echo $OVERLOAD_ERROR # output is blank
# I also tried these:
# ${i}_ERROR=$(echo ${i}_ERROR=$"1234") # command not found
# read ${i}_ERROR=$(echo ${i}_ERROR=$"1234") # it never terminates
How would I set a variable as I do from CLI, but in a script? thanks
When you use dynamic variables names instead of associative arrays, you really need to question your approach.
err_type=("OVERLOAD" "PANIC" "FATAL")
declare -A error
for type in "${err_type[#]}"; do
error[$type]=1234
done
Nevertheless, in bash you'd use declare:
declare "${i}_error=1234"
Your approach fails because you spawn a new shell, passing the command OVERLOAD_ERROR=1234, and then the shell exits. Your current shell is not affected at all.
Get out of the habit of using ALLCAPSVARNAMES. One day you'll write PATH=... and then wonder why your script is broken.
If the variable will hold a number, you can use let.
#!/bin/bash
ERR_TYPE=("OVERLOAD" "PANIC" "FATAL")
j=0
for i in "${ERR_TYPE[#]}"
do
let ${i}_ERROR=1000+j++
done
echo $OVERLOAD_ERROR
echo $PANIC_ERROR
echo $FATAL_ERROR
This outputs:
1000
1001
1002
I'd use eval.
I think this would be considered bad practice though (it had some thing to do with the fact that eval is "evil" because it allows bad input or something):
eval "${i}_ERROR=1234"

Unix: What does cat by itself do?

I saw the line data=$(cat) in a bash script (just declaring an empty variable) and am mystified as to what that could possibly do.
I read the man pages, but it doesn't have an example or explanation of this. Does this capture stdin or something? Any documentation on this?
EDIT: Specifically how the heck does doing data=$(cat) allow for it to run this hook script?
#!/bin/bash
# Runs all executable pre-commit-* hooks and exits after,
# if any of them was not successful.
#
# Based on
# http://osdir.com/ml/git/2009-01/msg00308.html
data=$(cat)
exitcodes=()
hookname=`basename $0`
# Run each hook, passing through STDIN and storing the exit code.
# We don't want to bail at the first failure, as the user might
# then bypass the hooks without knowing about additional issues.
for hook in $GIT_DIR/hooks/$hookname-*; do
test -x "$hook" || continue
echo "$data" | "$hook"
exitcodes+=($?)
done
https://github.com/henrik/dotfiles/blob/master/git_template/hooks/pre-commit
cat will catenate its input to its output.
In the context of the variable capture you posted, the effect is to assign the statement's (or containing script's) standard input to the variable.
The command substitution $(command) will return the command's output; the assignment will assign the substituted string to the variable; and in the absence of a file name argument, cat will read and print standard input.
The Git hook script you found this in captures the commit data from standard input so that it can be repeatedly piped to each hook script separately. You only get one copy of standard input, so if you need it multiple times, you need to capture it somehow. (I would use a temporary file, and quote all file name variables properly; but keeping the data in a variable is certainly okay, especially if you only expect fairly small amounts of input.)
Doing:
t#t:~# temp=$(cat)
hello how
are you?
t#t:~# echo $temp
hello how are you?
(A single Controld on the line by itself following "are you?" terminates the input.)
As manual says
cat - concatenate files and print on the standard output
Also
cat Copy standard input to standard output.
here, cat will concatenate your STDIN into a single string and assign it to variable temp.
Say your bash script script.sh is:
#!/bin/bash
data=$(cat)
Then, the following commands will store the string STR in the variable data:
echo STR | bash script.sh
bash script.sh < <(echo STR)
bash script.sh <<< STR

Understanding !# and $# in bash

I've just recently started programming scala, and in the book "Programming in Scala"(www.artima.com/pins1ed) the following method of executing scala scripts in linux is presented:
#!/bin/sh
exec scala "$0" "$#"
!#
// Say hello to the first argument
println("Hello, "+ args(0) +"!")
Now I've been using linux for a long time, but bash scripting is not my speciality. Now I can guess how this type of scrpt works(and it works beautifully), but I was wondering what do the !# and $# do exactly.
Thanks in advance for all the help!
Beautiful indeed. $0 and "$#" are positional paramters ($0 = command itself just like argv[0] in C, and argv[1]+ for "$#"), whereas #!* tells the shell, and sometimes the kernel if it recognizes it which program to execute for the file.
The thing that happens here actually is that the shell opens the script for input reading but on the point of exec, it transfers the input to scala, but scala wouldn't have to read it again from the beginning since the file descriptor is still open and so scala continues reading on the next line.
Rarely do I see scripts that do that with apparent and simple presentation of how it functions.
Note that exec replaces the process of the shell running the script and so it's like the shell becomes scala but scala would have the environment variable and opened handlers as the same.
UPDATE
Looks like I was wrong. Scala itself reads the whole but skips what it could see as header lines to it. So this is the real purpose of !#:
Script files may have an optional header that is ignored if present. There are two ways to format the header: either beginning with #! and ending with !#, or beginning with ::#! and ending with ::!#.
!# doesn't have anything to do with Bash. It's part of the Scala Language. It separates a non-Scala header from Scala Code in Script Mode.
"$#" represents all the script arguments.
You asked about what "$#" does exactly. It passes the arguments to the script in a non-word-splitting manner. Let's see some examples:
$ cat echowrap
#!/bin/sh
set -x
echo $*
echo $#
echo "$#"
$ ./echowrap oneword 'two words'
+ echo oneword two words
oneword two words
+ echo oneword two words
oneword two words
+ echo oneword 'two words'
oneword two words
In the first example, $* has split the input args so that echo sees three words.The second example $# behaves identically. The third example "$#" does not undergo word-splitting, therefore echo sees the same 2 args as were originally passed.
Consider a more useful example; if you called your script as
$ ./scalascript 'Joe Bloggs'
then try changing "$#" into $# or $*, the shell will pass two arguments to scala, scala will see args(0) and args(1), and the output of the test program will be different.

Resources