Bash string comparison syntax - linux

I was looking through the /etc/bash_completion script found in some Debian packages. I was interested in using the code that looks through a specific directory (/etc/bash_completion.d/ by default) and sources every file in that directory.
Unfortunately, trying to run the script causes errors under the Mac OS X version of bash. The lines in question are:
for i in $BASH_COMPLETION_DIR/*; do
[[ ${i##*/} != #(*~|*.bak|*.swp|\#*\#|*.dpkg*|.rpm*) ]] &&
[ \( -f $i -o -h $i \) -a -r $i ] && . $i
done
Specifically, my version of bash (3.2.17) chokes on the #() construction. I get that the point of that first test is to make sure we don't source any editor swap files or backups, etc. I'd like to understand exactly what that #() syntax does, and, if possible how to get something similar (and similarly elegant) running on my ancient copy of bash. Can anyone offer insight?

It's just an extension to the shell comparison which is equivalent to the grep "or" operator (|).
Depending on your bash version, it may not be available or you may have to set extglob with the shopt built-in. See the following session transcript:
pax#daemonspawn> $ bash --version
GNU bash, version 3.2.48(21)-release (i686-pc-cygwin)
Copyright (C) 2007 Free Software Foundation, Inc.
pax#daemonspawn> echo #(*~|*.pl)
bash: syntax error near unexpected token '('
pax#daemonspawn> shopt extglob
extglob off
pax#daemonspawn> shopt -s extglob
pax#daemonspawn> echo #(*~|*.pl)
qq.pl qq.sh~ xx.pl
pax#daemonspawn>
That allows the following to work:
?(pattern-list)
Matches zero or one occurrence of the given patterns
*(pattern-list)
Matches zero or more occurrences of the given patterns
+(pattern-list)
Matches one or more occurrences of the given patterns
#(pattern-list)
Matches one of the given patterns
!(pattern-list)
Matches anything except one of the given patterns
If you can't get it working with shopt, you can generate a similar effect with older methods such as:
#!/bin/bash
for i in $BASH_COMPLETION_DIR/*; do
# Ignore VIM, backup, swp, files with all #'s and install package files.
# I think that's the right meaning for the '\#*\#' string.
# I don't know for sure what it's meant to match otherwise.
echo $i | egrep '~$|\.bak$|\.swp$|^#*#$|\.dpkg|\.rpm' >/dev/null 2>&1
if [[ $? == 0 ]] ; then
. $i
fi
done
Alternatively, if there's multiple complex determinations that will decide whether you want it sourced, you can use a doit variable that's initially set to true, and set it to false if any of those conditions trigger. For example, the following script qq.sh:
#!/bin/bash
for i in * ; do
doit=1
# Ignore VIM backups.
echo $i | egrep '~$' >/dev/null 2>&1
if [[ $? -eq 0 ]] ; then
doit=0
fi
# Ignore Perl files.
echo $i | egrep '\.pl$' >/dev/null 2>&1
if [[ $? -eq 0 ]] ; then
doit=0
fi
if [[ ${doit} -eq 1 ]] ; then
echo Processing $i
else
echo Ignoring $i
fi
done
did this in my home directory:
Processing Makefile
Processing binmath.c
: : : : :
Processing qq.in
Ignoring qq.pl --+
Processing qq.sh |
Ignoring qq.sh~ --+--- see?
Processing qqin |
: : : : : |
Ignoring xx.pl --+

Related

Linux: Piping output to unique files

I have a folder filed with hundreds of text files which I want to run a Linux command called mint. This command outputs a text value which I want stored in unique files, one for each file I have in the folder. Is there a way to run the command using the * character to represent all my input files, while still piping the output to a file that is unique from each other file?
Example:
$ mint * > uniqueFile.krn
With the bugs fixed and caveats closed:
#!/bin/bash
# ^^^^ - bash, not sh, for [[ ]] support
for f in *.krn; do
[[ $f = *.krn ]] && continue # skip files already ending in .krn
mint "$f" >"$f.krn"
done
Or, with a prefix:
for f in *; do
[[ $f = int_* ]] && continue
mint "$f" >"int_$f"
done
You can also avoid recreating hashes that already exist unless the source file changed:
for f in *; do
# don't hash hash files
[[ $f = int_* ]] && continue
# if a non-empty hash file exists, and is newer than our source file, don't hash again
[[ -s "int_$f" && "int_$f" -nt "$f" ]] && continue
# ...if we got through the above conditions, then go ahead with creating a hash
mint "$f" >"int_$f"
done
To explain:
test -s filename is true only if a file by the given name exists and is non-empty
test file1 -nt file2 is true only if both files exist, and file1 is newer than file2.
[[ ]] is a ksh-extended shell syntax derived from that for the test command, adding support for pattern-matching tests (ie. [[ $string = *.txt ]] will be true only if $string expands to a value ending in .txt), and relaxing quoting rules (it's safe to write [[ -s $f ]], but test -s "$f" needs the quotes to work with all possible filenames).
Thanks for all the suggestions! Shiping's solution worked great, I just appended a prefix to the file name. Like so:
$ for file in * ; do mint $file > int_$file ; done
Self-answer moved from question and flagged Community Wiki; see What is the appropriate action when the answer to a question is added to the question itself?

Bash scripting wanting to find a size of a directory and if size is greater than x then do a task

I have put the following together with a couple of other articles but it does not seem to be working. What I am trying to do eventually do is for it to check the directory size and then if the directory has new content above a certain total size it will then let me know.
#!/bin/bash
file=private/videos/tv
minimumsize=2
actualsize=$(du -m "$file" | cut -f 1)
if [ $actualsize -ge $minimumsize ]; then
echo "nothing here to see"
else
echo "time to sync"
fi
this is the output:
./sync.sh: line 5: [: too many arguments
time to sync
I am new to bash scripting so thank you in advance.
The error:
[: too many arguments
seems to indicate that either $actualsize or $minimumsize is expanding to more than one argument.
Change your script as follows:
#!/bin/bash
set -x # Add this line.
file=private/videos/tv
minimumsize=2
actualsize=$(du -m "$file" | cut -f 1)
echo "[$actualsize] [$minimumsize]" # Add this line.
if [ $actualsize -ge $minimumsize ]; then
echo "nothing here to see"
else
echo "time to sync"
fi
The set -x will echo commands before attempting to execute them, something which assists greatly with debugging.
The echo "[$actualsize] [$minimumsize]" will assist in trying to establish whether these variables are badly formatted or not, before the attempted comparison.
If you do that, you'll no doubt find that some arguments will result in a lot of output from the du -m command since it descends into subdirectories and gives you multiple lines of output.
If you want a single line of output for all the subdirectories aggregated, you have to use the -s flag as well:
actualsize=$(du -ms "$file" | cut -f 1)
If instead you don't want any of the subdirectories taken into account, you can take a slightly different approach, limiting the depth to one and tallying up all the sizes:
actualsize=$(find . -maxdepth 1 -type f -print0 | xargs -0 ls -al | awk '{s += $6} END {print int(s/1024/1024)}')

Too many arguments in for

In both for statements, I am getting the following error:
./count_files.sh: line 21: [: too many arguments
./count_files.sh: line 16: [: too many arguments.
Can anyone help me ?
#!/bin/bash
files=($(find /usr/src/linux-headers-3.13.0-34/include/ -type f -name '[aeiou][a-z0-9]*.h'))
count=0
headerfiles=($(find /usr/src/linux-headers-3.13.0-34/include/ -type f -name '[_a-zA-Z0-9]*.h' | grep -v "/linux/"))
for file in "${files[#]}"
do
if ! [ grep -Fxq "linux/err.h" $file ];
then
localcount=0
for header in "${headerfiles[#]}"
do
if [ grep -Fxq $header $file ];
then
localcount=$((localcount+1))
if [ $localcount -eq 3 ];
then
count=$(($count+1))
break
fi
fi
done
localcount=0
fi
done
echo $count
One of the problem lines is:
if ! [ grep -Fxq "linux/err.h" $file ];
The semicolon at the end is unnecessary unless the then is on the same line; it is, however, harmless.
It looks as though you want to execute the grep command and check whether it produces any output. However, you've simply provided the test (aka [) command with four string arguments (plus the closing ] for 5 in total), the second of which is not one of the options recognized by test.
You might have meant to use this:
if ! [ -n "$(grep -Fxq "linux/err.h" "$file")" ]
(unless you meant -z instead of -n; the negations are confusing me). But if you're interested in whether grep found anything, you can simply test the exit status of grep:
if grep -Fxq "linux/err.h" "$file"
Hmmm...the -q is 'quiet' mode; so in fact the string test won't work since grep produces no output. You want the direct test of the exit status, possibly preceded by the ! logical not operator.
You shouldn't use square brackets around the grep.
In shell scripts square brackets are not used for grouping, [ is a command in its own right (an alias for test), and it is the [ command that is complaining that you've given it too many arguments.
Just make the call without brackets
if ! grep ....
Change the fors to whiles with reads:
...
echo "${files}" | while read file ; do
...
echo "${headerfiles}" | while read header ; do
...
done
...
done
...

basic bash syntax issue - syntax error near unexpected token

I think my script does what its meant to do, at first I just had
#!/bin/bash
for file in /home/parallels/Desktop/trashcan/*
do
echo "Would you like to delete - " $file
done
I then wanted to add the obvious missing functionality so I now have
#!/bin/bash
for file in /home/parallels/Desktop/trashcan/*
do
echo "Would you like to delete - " $file
read line
if [$line == y|Y]
sudo rm $file
fi
done
Thats where I'm at now, I did at first try to use a case statement instead of the if as I have a working script with the case statement I'd need but simply copying it over gives me the same error - syntax error near unexpeted token, I get this for fi and done
[ is a command, so it must be separated by whitespace from its first argument:
if [ "$line" = y ] || [ "$line" = Y ]; then
sudo rm "$file"
fi
If you are using bash, you can replace the standard usage shown above with the more concise
if [[ $line = [yY] ]]; then
sudo rm "$file"
fi
As Keith Thompson pointed out, only an answer of exactly 'y' or 'Y' will allow the file to be removed. To allow 'yes' or 'Yes' as well, you can use
shopt -s extglob
if [[ $line = [yY]?(es) ]]
(The shopt line is only required for earlier versions of bash. Starting with version 4, extended patterns are the default in a conditional expression.)
The if part should be
if [ "$line" = "y" ] || [ "$line" = "Y" ]
then
sudo rm $file
fi
I faced similar problem. I had opened the .sh file in windows and Windows has added CRLF at the end of every line.
I found this out by running
cat --show-nonprinting filename.extension
E.g.
cat --show-nonprinting test.sh
Then, I converted it using
dos2unix filename.extension
E.g.
dos2unix myfile.txt
dos2unix myshell.sh

Parsing result of Diff in Shell Script

I want to compare two files and see if they are the same or not in my shell script, my way is:
diff_output=`diff ${dest_file} ${source_file}`
if [ some_other_condition -o ${diff_output} -o some_other_condition2 ]
then
....
fi
Basically, if they are the same ${diff_output} should contain nothing and the above test would evaluate to true.
But when I run my script, it says
[: too many arguments
On the if [....] line.
Any ideas?
Do you care about what the actual differences are, or just whether the files are different? If it's the latter you don't need to parse the output; you can check the exit code instead.
if diff -q "$source_file" "$dest_file" > /dev/null; then
: # files are the same
else
: # files are different
fi
Or use cmp which is more efficient:
if cmp -s "$source_file" "$dest_file"; then
: # files are the same
else
: # files are different
fi
There's an option provided precisely for doing this: -q (or --quiet). It tells diff to just let the exit code indicate whether the files were identical. That way you can do this:
if diff -q "$dest_file" "$source_file"; then
# files are identical
else
# files differ
fi
or if you want to swap the two clauses:
if ! diff -q "$dest_file" "$source_file"; then
# files differ
else
# files are identical
fi
If you really wanted to do it your way (i.e. you need the output) you should do this:
if [ -n "$diff_output" -o ... ]; then
...
fi
-n means "test if the following string is non-empty. You also have to surround it with quotes, so that if it's empty, the test still has a string there - you're getting your error because your test evaluates to some_other_condition -o -o some_other_condition2, which as you can see isn't going to work.
diff is for comparing files line by line for processing the differences tool like patch . If you just want to check if they are different, you should use cmp:
cmp --quiet $source_file $dest_file || echo Different
diff $FILE $FILE2
if [ $? = 0 ]; then
echo “TWO FILES ARE SAME”
else
echo “TWO FILES ARE SOMEWHAT DIFFERENT”
fi
Check files for difference in bash
source_file=abc.txt
dest_file=xyz.txt
if [[ -z $(sdiff -s $dest_file $source_file) ]]; then
echo "Files are same"
else
echo "Files are different"
fi
Code tested on RHEL/CentOS Linux (6.X and 7.X)

Resources