Batch Script - Comma causing problems when writing numerical value to txt file - string

So this is an example of a script I'm writing to produce txt file containing a list of correctly formatted commands to be passed to another system, based on a long series of questions.
If objectName, objectNumber, or objectCategory are assigned an alphanumeric value, the script will write correctly to the txt file. However, if I were to assign a numerical value, the value is not written to the txt file.
I'm guessing this is a syntax issue related to the use of a comma, as I can replace it with with pretty much anything and the script will behave, though unfortunately it has to be a comma.
#ECHO OFF
SET objectCounter=1
SET /p objectName=What is the name of the object %objectCounter%?:
#ECHO OBJECTNAME%objectCounter%,%objectName%> objects.txt
SET /p objectNumber=How many of object %objectCounter% are there?:
#ECHO OBJECTNUMBER%objectCounter%,%objectNumber%>> objects.txt
SET /p objectCategory=Which group does object %objectCounter% belong to?:
#ECHO OBJECTCATEGORY%objectCounter%,%objectNumber%>> objects.txt
This is an example of the output to the txt file if objectNumber is assigned a value of 1:
OBJECTNAME1,Apple
OBJECTNUMBER1,
OBJECTCATEGORY1,Fruit
And this is an example of the output to the txt file if objectNumber is assigned a value of 2 or more:
OBJECTNAME1,Apple
OBJECTCATEGORY1,Fruit

This is a simplification: a echo command to send the text data 1 to a file:
echo data 1>somewhere.txt
Here it is easy to see that the 1 will be handled by the parser as the stream number to redirect, not data to send to the file.
But the question is not using a space, but a comma. Why the same behaviour? Because from the parser point of view, spaces, tabs, commas, semicolons, parenthesis and equals are delimiters. All these lines fail the same way (tabs omited)
echo data 1>>somewhere.txt
echo data,1>>somewhere.txt
echo data;1>>somewhere.txt
echo data=1>>somewhere.txt
echo data(1>>somewhere.txt
echo data)1>>somewhere.txt
How to handle it? It is necessary to separate the digit from the redirection, so we can change the order in the line
>somewhere.txt echo data,1
or force the parser see the separation
(echo data,1)>somewhere.txt
or, if the data is inside a variable, we can also use delayed expansion
set "n=1"
setlocal enabledelayedexpansion
echo data,!n!>somewhere.txt
Of course, we can also do
echo data,1 >>somewhere.txt
including a space between the data and the redirection, but the space will be included in the redirected data.
Another option is to reorganize the code
#ECHO OFF
SET objectCounter=1
SET /p "objectName=What is the name of the object %objectCounter%?: "
SET /p "objectNumber=How many of object %objectCounter% are there?: "
SET /p "objectCategory=Which group does object %objectCounter% belong to?: "
> objects.txt (
ECHO OBJECTNAME%objectCounter%,%objectName%
ECHO OBJECTNUMBER%objectCounter%,%objectNumber%
ECHO OBJECTCATEGORY%objectCounter%,%objectCategory%
)

It looks like it has to do with output redirection in conjunction with the comma. I think, with the comma in there, the numeric value is being bound to the redirection rather than to the thing being output.
In other words, while:
set x=1
echo xyzzy%x%>qq.txt
will work (the thing being output is xyzzy%x% with a redirection of >qq.txt), the following will not:
set x=1
echo xyzzy,%x%>qq.txt
(presumably because the thing being output is xyzzy, with a redirection operation 1>qq.txt which is the same as >qq.txt). That also explains the difference you're seeing between 1 and other numbers since 1 is standard output.
You can see a similar problem even without variable expansion:
C:\pax> echo xyzzy1>qq.txt
C:\pax> type qq.txt
xyzzy1
C:\pax> echo xyzzy,1>qq.txt
C:\pax> type qq.txt
xyzzy,
One way around it is to reorganise your components so that the numeric value cannot be tied to the redirection:
>>objects.txt ECHO OBJECTNUMBER%objectCounter%,%objectNumber%
I tend to prefer putting them at the start since using something like:
echo xyzzy >file
will actually output xyzzy and the space immediately before the >.
Modifying the lines like that (and fixing your third echo so it outputs the category rather than the number again) gives you:
What is the name of the object 1?: Apple
How many of object 1 are there?: 1
Which group does object 1 belong to?: Fruit
with the resultant file being:
OBJECTNAME1,Apple
OBJECTNUMBER1,1
OBJECTCATEGORY1,Fruit

Related

Case statement with multiple possible values ignored

I would like to execute a shell script which is having below code .
value=`echo "false"`
case $1 in "26492|26851|27407|26493")
value=`echo "true"`
;;
esac
If the first argument is one of the values specified, a word true will be printed otherwise, false will be printed
26492|26851|27407|26493
I am not getting the required output.
I have executed like this sh -x script name 27407.
Can some one please help in this ?
There are two issues here:
You can't enclose the option indicators (the pipe symbols, |) within the quotation marks, since that makes bash treat that entire string as a single possible value.
Your echo statement is enclosed in back-ticks, and that's assigned to value. So what happens is that instead of getting true as the output, you're getting the value of value changed.
Try:
case $1 in 26492|26851|27407|26493)
echo "true"
;;
esac

Windows CMD expand filename if file has .double.extension

I'm writing a script that converts a Markdown file to a PDF, facilitated by Pandoc.
So if you drag C:\Users\User\Documents\English\PAPER1.md onto the script, it'll create C:\Users\User\Documents\English\PDFs\PAPER1.pdf.
This is achieved in the header of the script via
set INPUT=%1
set PDFDIR=%~dp1\PDFs
set PDF=%PDFDIR%\%~n1.pdf
However, in certain circumstances, the input filename will be Something.md.txt, in which case I only still want to output Something.pdf. (Removing more than three extensions will probably not be necessary or desirable.)
But the current setup only strips one extension, producing Something.md.pdf.
However, %~nn1.pdf does not work, nor does set p=%~n1 set PDF=%~np, giving
The following usage of the path operator in batch-parameter
substitution is invalid: %~np
How do I get the bare filename of a file with "two extensions"?
I'd change every line from the provided header of the script to:
Set "INPUT=%~1"
Set "PDFDIR=%~dp1PDFs"
For %%A In ("%~dpn1") Do Set "PDF=%PDFDIR%\%%~nA.pdf"
Because there's no surety of the input content, please use best practice and always reference these variables wherever possible using doublequotes:
Echo "%INPUT%"
Echo "%PDFDIR%"
Echo "%PDF%"

Read csv file titles with blanc inside

I'm trying to read a the csv file :
"EDP";"Picture 1";"Picture 2";"Picture 2"
"1001480210";"T244.png";"";""
I create a script to read this files :
cd ~/86829/
while IFS=';' read "EDP" "Picture 1" "Picture 2" "Picture 3"
When i run the script, i got this error :
./RUN_CopyPictures.sh: 9: read: Picture 1: bad variable name
When i change titles to "Picture1";"Picture2";"Picture2" , and the script to while IFS=';' read EDP Picture1 Picture2 Picture3
it's work
So my question is very clear :
How can i read a title of csv file with blancs inside ?
The problem is not about reading "titles" (which is usually just the first line of the CSV file).
Instead, your problem (that causes the error) is that a line read x assigns a value to the variable named x (which can then be referenced via $x).
In your example you use read "Picture 1" which effectively tries to assign a value to the variable named Picture 1.
Since bash forbids variable names with spaces (just like probably any other non-esoteric programming language), this gives you an error.
The solution is to use legible and legit variable names:
while IFS=';' read edp pic1 fusel y
do
echo "Picture 1 is ${pic1}"
echo "Picture 2 is ${fusel}"
echo "Picture 3 is ${y}"
done
There are a number of naming-schemes for variable names, common ones include all lowercase with (or without) underscores to separate words, or CamelCase.
on "titles"
Your script doesn't know anything about what you refer to as "titles".
CSV doesn't know anything about "titles" either.
CSV is simply a format that has both lines (rows) and colums, as opposed to simpler text files that only have lines.
And just as a text-file doesn't have a notion of a "heading", a CSV file has no "titles".
Popular CSV-exporting software might, however, assign a special meaning to the very first row in a CSV-file and (ab)use it for title-information (so the content is only a label, whereas the actual column-content can be something different)
In any case, the IFS=';' read ... part of your script doesn't do anything with titles; it simply extracts multiple fields from a single line if input data and assigns these fields to variables.
The name of these variables can be totally arbitrary (as long as they conform to the bash syntax for variable names), and need not have anything to do with the content of the file.

Extracting string after last instance of delimiter in a Batch file

I'm working on writing a Windows batch file that is called by a Java program. A number of different strings denoting file directories are passed into the batch file as parameters. I am trying to figure out how to extract a string after the last instance of the "\". For example, I have three directories:
C:\Users\owner\Documents
C:\Users\owner\Videos
C:\Users\owner\Pictures\Trip
I would like to split it so that the strings become:
Documents
Videos
Trip
How would you guys suggest I do this?
EDIT: A follow-up question has been asked here: For loop in Windows batch file: Error: "The syntax of the command is incorrect"
After assigned one parameter to "param" variable, use:
for %%a in (%param:\= %) do set lastDir=%%a
This method works as long as the last directory does NOT have spaces. This detail may be fixed, if needed. The processing of all parameters would be like this:
:nextParam
set "param=%~1"
if not defined param goto endParams
for %%a in (%param:\= %) do set lastDir=%%a
echo Last dir: %lastDir%
shift
goto nextParam
:endParams
Or, in a simpler way (with no spaces restrictions):
:nextParam
if "%~1" equ "" goto endParams
echo Last dir: %~N1
shift
goto nextParam
:endParams
If the strings are passed as arguments 1-3. you can use %~n1, %~n2, %~n3 to get the last folder in the path.

How to get a variable value if variable name is stored as string?

How can I retrieve a bash variable value if I have the variable name as string? var1="this is the real value"
a="var1"
Do something to get value of var1 just using variable a.
Context:
I have some AMI's (Amazon Machine Image) and I want to fire up a few instances of each AMI. As soon as they finish booting, I want to setup each instance according to its AMI type. I don't want to bake lots of scripts or secret keys inside any AMI so I prepared a generalized startup script and I put it on S3 with a publicly accessible link. In rc.local I put small piece of code which fetches the startup script and executes it. This is all I have in the AMIs. Then each AMI accesses a common configuration script which is applicable to all AMIs and special setup scripts for each. These scripts are private and require a signed URL to access them.
So now, when I fire an instance of an AMI (my_private_ami_1), I pass a signed URL for one more file presented on S3 which contains signed URL for all private scripts in terms of key/value pair.config_url="http://s3.amazo.../config?signature"
my_private_ami_1="http://s3.amazo.../ami_1?signature"
...
When the startup script runs, it downloads the above file and source's it. Then it checks for its AMI type and picks the correct setup script for itself.
ami\_type=GET AMI TYPE #ex: sets ami\_type to my\_private\_ami\_1
setup\_url=GET THE SETUP FILE URL BASED ON AMI\_TYPE # this is where this problem arises
So now I can have a generic code which can fire instances irrespective of their AMI types and instances can take care of themselves.
You can use ${!a}:
var1="this is the real value"
a="var1"
echo "${!a}" # outputs 'this is the real value'
This is an example of indirect parameter expansion:
The basic form of parameter expansion is ${parameter}. The value of
parameter is substituted.
If the first character of parameter is an exclamation point (!), it
introduces a level of variable indirection. Bash uses the value of the
variable formed from the rest of parameter as the name of the
variable; this variable is then expanded and that value is used in the
rest of the substitution, rather than the value of parameter itself.
X=foo
Y=X
eval "Z=\$$Y"
sets Z to foo.
For my fellow zsh users, the way to accomplish the same thing as the accepted answer is to use:
echo ${(P)a} # outputs 'this is the real value'
It is appropriately called Parameter name replacement
This forces the value of the parameter name to be interpreted as a
further parameter name, whose value will be used where appropriate.
Note that flags set with one of the typeset family of commands (in
particular case transformations) are not applied to the value of name
used in this fashion.
If used with a nested parameter or command substitution, the result of
that will be taken as a parameter name in the same way. For example,
if you have ‘foo=bar’ and ‘bar=baz’, the strings ${(P)foo},
${(P)${foo}}, and ${(P)$(echo bar)} will be expanded to ‘baz’.
Likewise, if the reference is itself nested, the expression with the
flag is treated as if it were directly replaced by the parameter name.
It is an error if this nested substitution produces an array with more
than one word. For example, if ‘name=assoc’ where the parameter assoc
is an associative array, then ‘${${(P)name}[elt]}’ refers to the
element of the associative subscripted ‘elt’.
Modified my search keywords and Got it :). eval a=\$$a Thanks for your time.
In bash 4.3+, you can use declare -n:
#!/usr/bin/env bash
var="this is the real value"
var_name="var"
declare -n var_ref=$var_name
echo "${var_ref}"
Had the same issue with arrays, here is how to do it if you're manipulating arrays too :
array_name="ARRAY_NAME"
ARRAY_NAME=("Val0" "Val1" "Val2")
ARRAY=$array_name[#]
echo "ARRAY=${ARRAY}"
ARRAY=("${!ARRAY}")
echo "ARRAY=${ARRAY[#]}"
echo "ARRAY[0]=${ARRAY[0]}"
echo "ARRAY[1]=${ARRAY[1]}"
echo "ARRAY[2]=${ARRAY[2]}"
This will output :
ARRAY=ARRAY_NAME[#]
ARRAY=Val0 Val1 Val2
ARRAY[0]=Val0
ARRAY[1]=Val1
ARRAY[2]=Val2
In bash 4.3, the '-v' test for set variables was introduced. At the same time, 'nameref' declaration was added. These two features together with the indirection operator (!) enable a simplified version of the previous example:
get_value()
{
declare -n var_name=$1
if [[ -v var_name ]]
then
echo "${var_name}"
else
echo "variable with name <${!var_name}> is not set"
fi
}
test=123
get_value test
123
test="\$(echo \"something nasty\")"
get_value test
$(echo "something nasty")
unset test
get_value test
variable with name <test> is not set
As this approach eliminates the need for 'eval', it is safer.
This code checked under bash 5.0.3(1).
modern shells already support arrays( and even associative arrays). So please do use them, and use less of eval.
var1="this is the real value"
array=("$var1")
# or array[0]="$var1"
then when you want to call it , echo ${array[0]}
Based on the answer: https://unix.stackexchange.com/a/111627
###############################################################################
# Summary: Returns the value of a variable given it's name as a string.
# Required Positional Argument:
# variable_name - The name of the variable to return the value of
# Returns: The value if variable exists; otherwise, empty string ("").
###############################################################################
get_value_of()
{
variable_name=$1
variable_value=""
if set | grep -q "^$variable_name="; then
eval variable_value="\$$variable_name"
fi
echo "$variable_value"
}
test=123
get_value_of test
# 123
test="\$(echo \"something nasty\")"
get_value_of test
# $(echo "something nasty")
VALUE=$(eval "echo \$$SOME_VAR_NAME")
Example
SSH_KEY_FILE_PATH_FOO="/tmp/key"
SSH_KEY_FILE_PATH_VAR_NAME_PREFIX="SSH_KEY_FILE_PATH"
SSH_KEY_FILE_PATH_VAR_NAME_SUFFIX="FOO"
SSH_KEY_FILE_PATH=$(eval "echo \$${SSH_KEY_FILE_PATH_VAR_NAME_PREFIX}_${SSH_KEY_FILE_PATH_VAR_NAME_SUFFIX}")
echo "$SSH_KEY_FILE_PATH"
/tmp/key

Resources