Unix File extension validation Fails - shell

Something I am missing that I am not able to figure out.. Need some thoughts..
I am trying to check file extensions in a directory only file extensions I get .txt or .TXT ) .. but both should be treated as different as I am performing different validations for .txt and .TXT files..
I have the below files
aa.394.63.txt
aa.394.23.TXT
Here is my code
for file in "$SEARCH_DIR"/*; do
extn=$(echo $file | awk -F '.' '{print $NF}')
echo "extn:" $extn
if [ $extn=="txt" ]; then
echo "txt Loop"
elif [$extn=="TXT" ]; then
echo "TXT loop"
fi
But this script always be true for the "txt" validations never goes to the "TXT loop".. I think unix is case sensitive and it should be treated as separate.. Pls advise what am I missing ..

You're using test in the form of [] to test your conditions. You must include spaces around the brackets and the equality operators.
From the test man page:
[ is a synonym for test but requires a final argument of ]
...
Spaces around the brackets are important - each operator and operand must be a separate argument.
https://ss64.com/bash/test.html
This means that you need to pay careful attention to spaces in your test constructs. You should also note that variables should be quoted when you're testing them with [], as they may have undergone word splitting (not relevant in this case, but probably a good habit).
Because you're using [] to test conditons, rather than the bash [[]] construct, you should use a single = framed with whitespace as a test for string equality.
The following is a slightly amended version of your code and should work:
#!/bin/bash
SEARCH_DIR=./search
for file in "$SEARCH_DIR"/*; do
extn=$(echo $file | awk -F '.' '{print $NF}')
echo "extn:" "$extn"
if [ "$extn" = "txt" ]; then
echo "txt Loop"
elif [ "$extn" = "TXT" ]; then
echo "TXT loop"
fi
done
References
test man page
Comparison operators in bash

Related

If else condition match statements not working inside shell of jenkinsfile

I have below statement in one of the steps in Jenkinsfile
steps {
sh '''
file=/sql/common/file1.sql
echo $file
if ["$file" = *"/common"* ]; then
echo "changes found in common directory "
fi
'''
}
For some reason shell is not working properly inside jenkinsfile. how do we
compare strings in shell in Jenkinsfile? do we have any specific syntax for these? Jenkins give error if I use == operator to compare the strings.
My assumption was shell should work same way in Jenkinsfile once we declare it inside sh '''. Is that not the case?
["$file"
is invalid. There must be a space between [ and the argument. [ is a command.
if [ "$file" = *"/common"* ];
doesn't mean what you think it does. *"/common"* undergoes filename expansion, so it is replaced by a list of files that match the pattern. Because there are most probably many files that match the filename expansion, [ program exits with some kind of a a syntax error.
If you want to match a string against a pattern in POSIX shell, either use grep with a regular expression:
if printf "%s\n" "$file" | grep -q '.*/common.*'; then
or use case with glob:
if case "$file" in *"/common"*) true; ;; *) false; ;; esac; then

Is using builtins to enhance performance negated by gratuitous use of subshells?

I'm writing a script where I need to make sure a string contains a comma. If it doesn't I need the script to exit. Consider the below, where my intent is to only use builtins to enhance performance:
#!/bin/sh
check_for_commas='This string must contain a comma'
comma_found='0'
iterate_through_string="$check_for_commas"
while [ -n "$iterate_through_string" ]; do
char="$(printf '%.1s' "$iterate_through_string")"
if [ "$char" = ',' ]; then
comma_found='1'
break
fi
iterate_through_string="${iterate_through_string#?}"
done
if [ "$comma_found" != '1' ]; then
echo 'Your string does not contain a comma. Exiting...'
exit
else
echo 'Found a comma in the string. Script can continue...'
fi
I am using command substitution in this script, which spawns a subshell for every single character it iterates through. Compare with this:
#!/bin/sh
check_for_commas='This string must contain a comma'
if [ "$(echo "$check_for_commas" | grep -q -F ','; echo "$?")" = '1' ]; then
echo 'Your string does not contain a comma. Exiting...'
exit
else
echo 'Found a comma in the string. Script can continue...'
fi
I clearly don't mind doing a little extra work to squeeze out extra performance. But I'm concerned that using so many subshells has defeated my whole initial intent.
Does my pursuit of only using builtins to enhance performance become useless when gratuitous use of subshells comes into the picture?
Command substitutions, as in $(printf ...), are indeed expensive -- and you don't need them for what you're doing here.
case $check_for_commas in
*,*) echo "Found a comma in the string";;
*) echo "No commas present; exiting"; exit 1;;
esac
In the more general case -- a fork() alone costs less than a fork()/execve() pair, so it's cheaper to have a single subshell than a single external-command invocation; but if you're comparing a loop generating multiple subshells vs a single external-command invocation, which is cheaper depends on how many times your loop will iterate (and how expensive each of these things is on your operating system -- forks are traditionally extra expensive on Windows, for example), and is as such a fact-intensive investigation. :)
(Speaking to the originally proposed code -- note that ksh93 will optimize away the fork in the specific var=$(printf ...) case; by contrast, in bash, you need to use printf -v var ... to get the same effect).
Here's a short POSIX shell function that uses a combined remove matching prefix pattern and remove matching suffix pattern, and test, (or rather [ which is the same thing), to return a true flag if there's a comma:
chkcomma(){ [ "${1#${1%,*}}" ] ; }
Example without comma:
chkcomma foo && echo comma found || echo no comma
Output:
no comma
Example with comma:
chkcomma foo,bar && echo comma found || echo no comma
Output:
comma found
This can be further abstracted to find substrings using globbing:
# Usage: instr globpattern string
# returns true if found, false if not.
instr(){ [ "${2#${2%${1}*}}" ] ; }
Example:
instr '[Mm]oo' mood && echo found
Output:
found

Bash script creates a file which it shouldn't be

This following script will creates a file named "Happy", i couldn't figure out why, can someone try this script and tell me what is happening?
Thanks!
#!/bin/bash
str1=""
str2="Sad"
str3="Happy"
if [ "$str2">"$str3" ]
then echo "$str2 is greater than $str3"
elif [ "$str2"<"$str3" ]
then echo "$str2 is less than $str3"
fi
[ is just a (silly) alias for the test command; everything following it (including the mandatory closing ]) is an argument. (What you have is treated the same as if test "$str2">"$str3".)
There are two issues:
The operands and the operators need to be separated by whitespace.
# still not quite right, but ...
if [ "$str2" > "$str3" ]
Since > and < are interpreted as redirection operators by the shell, they have to be escaped so that they are passed as arguments to [.
if [ "$str2 \> "$str3" ]
(You might think just escaping the operator would be sufficient; however, "$str2"\>"$str3" would be treated as a single string argument to test rather than three separate arguments which test will interpret as an expression. test "$str2"\>"$str3" would simply check if the single argument is empty or not.)
Since you are using bash, it's far simpler to just use [[ instead of [. [[ is not a regular command; it's special syntax recognized by bash, so that normal processing rules don't apply. Most relevant here is that > is a string comparison operator, not a redirection operator, so it doesn't need to be escaped.
if [[ $str2 > $str3 ]]
If you rewrite the code as follows:
#!/bin/bash
str1=""
str2="Sad"
str3="Happy"
if [[ "$str2" > "$str3" ]]
then echo "$str2 is greater than $str3"
elif [[ "$str2" < "$str3" ]]
then echo "$str2 is less than $str3"
fi
then the comparisons should occur correctly and you can avoid inadvertent file creation. The extra pair of "[" and "]" takes the code out of test context, allowing for comparison and avoids file creation. More info here which states the following:
Note that the ">" needs to be escaped within a [ ] construct.
...
Note that the "<" needs to be escaped within a [ ] construct.
The reason is that in a test context, i.e. using only a single pair of square brackets as in the OP code, ">" and "<" are interpreted as redirection operators. So, instead of meaning greater than and less than respectively, ">" means direct the output of a command to a file whereas "<" means give input to a command.

file without extension: how to notice in bash script?

I made a very simple script which tells me a file name and extension.
The script works as follows:
for file in * ; do
if [[ -f $file ]] ; then
filename=${file##*/}
basename=${filename%\.*}
extension=${filename##*.}
if [[ -n $extension ]] ; then
echo "FILE: " $basename " ; ESTENSIONE " $extension
fi
fi
done
The problem is that when I have a file without extension (e.g. Makefile) it says that the extension is the filename itself (e.g. extension= Makefile).
Am I doing something wrong?
Well, the result you get is the expected one; I don't know if that means you're doing something wrong or not.
The way the pattern replacements work is that if the pattern doesn't match, nothing is replaced. Here you have ${filename##*.} which says remove all characters up to and including the final period. But if there's no period in the name, then the pattern doesn't match and nothing is removed, so you simply get the same result as ${filename}.
I should point out that the backslash in ${filename%\.*} is useless: the pattern here is shell globbing not regular expressions, so you don't need to escape a period. You can just write ${filename%.*}.
ETA:
There's no way to do what you want in one step. You have two choices; you can either test to see if the extension is the same as the filename and if so set it to empty:
extension=${filename##*.}
[ "$extension" = "$filename" ] && extension=
or you can strip off the basename, which you already computed, then get rid of any leading periods:
extension=${filename#$basename}
extension=${extension##*.}
Extensions don't have any privileged status in Unix file systems; they are just a part of the file name that people treat specially. You'll have to check if the file contains a . first.
basename=${filename%\.*}
if [[ $filename = *.* ]]; then
extension=${filename##*.}
echo "FILE: " $basename " ; ESTENSIONE " $extension
else
extension=""
fi

Bash - if and for statements

I am little unfamiliar with the 'if...then...fi' and the 'for' statements syntax.
Could anyone explain what the "$2/$fn" and "/etc/*release" in the code snippets below mean?...specifically on the use of the forward slash....and the asterisk...
if [ -f "$filename" ]; then
if [ ! -f "$2/$fn" ]; then
echo "$fn is missing from $2"
missing=$((missing + 1))
fi
fi
and
function system_info
{
if ls /etc/*release 1>/dev/null 2>&1; then
echo "<h2>System release info</h2>"
echo "<pre>"
for i in /etc/*release; do
# Since we can't be sure of the
# length of the file, only
# display the first line.
head -n 1 $i
done
uname -orp
echo "</pre>"
fi
} # end of system_info
...thx for the help...
/etc/*release : here the * will match any number of any characters, so any thing /etc/0release , /etc/asdfasdfr_release etc will be matched. Simply stated, it defined all the files in the /etc/ directory which ends with the string release.
The $2 is the 2nd commandline argument to the shell script, and $fn is some other shell variable. The "$2/$fn" after the variable substitutions will make a string, and the [ -f "$2/$fn" ] will test if the string formed after the substitution forms a path to a regular file which is specified by the -f switch. If it is a regular file then the body of if is executed.
In the for loop the loop will loop for all the files ending with the string release in the directory /etc (the path). At each iteration i will contain the next such file name, and for each iteration the first 1 line of the file is displayed with the head command by getting the file name from variable i within the body.
It is better to check the manual man bash and for if condition check man test . Here is a good resource: http://tldp.org/LDP/Bash-Beginners-Guide/html/
The forward slash is the path separator, and the * is a file glob character. $2/$fn is a path where $2 specifies the directory and $fn is the filename. /etc/*release expands to the space separated list of all the files in /etc whose name ends in "release"
Dollar sign marks variable. The "-f" operator means "file exsists".
So,
[ -f "$filename" ]
checks if there is file named the same as value contained in $filename variable.
Simmilar, if we assume that $2 = "some_folder", and $fn = "some_file", expression
[ ! -f "$2/$fn" ]
returns true if file some_folder/some_file doesn't exsist.
Now, about asterisk - it marks "zero or more of any character(s)". So, expression:
for i in /etc/*release; do
will iterate trough all folders named by that pattern, for example:
/etc/release, /etc/666release, /etc/wtf_release...
I hope this helps.

Resources