How can I compare two strings in bash? - string

I am trying to remove all ".s" files in a folder that can be derived by ".c" source files.
This is my code
for cfile in *.c; do
#replace the last letter with s
cfile=${cfile/%c/s}
for sfile in *.s; do
#compare cfile with sfile; if exists delete sfile
if [ $cfile==$sfile ]; then
rm $sfile;
fi
done
done
But this code deletes all the ".s" files. I think it's not comparing the filenames properly.
Can someone please help.

The canonical way to compare strings in bash is:
if [ "$string1" == "$string2" ]; then
this way if one of the strings is empty it'll still run.

You can use it like this:
[[ "$cfile" = "$sfile" ]] && rm "$sfile"
OR
[[ "$cfile" == "$sfile" ]] && rm "$sfile"
OR by using old /bin/[ (test) program
[ "$cfile" = "$sfile" ] && rm "$sfile"

Saying
if [ $chile==$sfile ]; then
would always be true since it amounts to saying
if [ something ]; then
Always ensure spaces around the operators.
The other problem is that you're saying:
cfile=${cfile/%c/s}
You probably wanted to say:
sfile=${cfile/%c/s}
And you need to get rid of the inner loop:
for sfile in *.s; do
done
Just keep the comparison code.

I think the most simpliest solution would be:
for cfile in *.c ; do rm -f "${cfile%.c}.s" ; done
It just lists all the .c files and try to delete the corresponding .s file (if any).

for cFile in *.c; do
sFile="${cFile%c}s"
if [[ -f "$sFile" ]]; then
echo "delete $sFile"
fi
done
The actual deletion of the files I leave as an exercise. :-)
You can also just brute force and delete everything and redirecting the error messages to /dev/null:
for cFile in *.c; do
sFile="${cFile%c}s"
rm "$sFile" &> /dev/null
done
but this will be slower of course.

Related

extracting files that doesn't have a dir with the same name

sorry for that odd title. I didn't know how to word it the right way.
I'm trying to write a script to filter my wiki files to those got directories with the same name and the ones without. I'll elaborate further.
here is my file system:
what I need to do is print a list of those files which have directories in their name and another one of those without.
So my ultimate goal is getting:
with dirs:
Docs
Eng
Python
RHEL
To_do_list
articals
without dirs:
orphan.txt
orphan2.txt
orphan3.txt
I managed to get those files with dirs. Here is me code:
getname () {
file=$( basename "$1" )
file2=${file%%.*}
echo $file2
}
for d in Mywiki/* ; do
if [[ -f $d ]]; then
file=$(getname $d)
for x in Mywiki/* ; do
dir=$(getname $x)
if [[ -d $x ]] && [ $dir == $file ]; then
echo $dir
fi
done
fi
done
but stuck with getting those without. if this is the wrong way of doing this please clarify the right one.
any help appreciated. Thanks.
Here's a quick attempt.
for file in Mywiki/*.txt; do
nodir=${file##*/}
test -d "${file%.txt}" && printf "%s\n" "$nodir" >&3 || printf "%s\n" "$nodir"
done >with 3>without
This shamelessly uses standard output for the non-orphans. Maybe more robustly open another separate file descriptor for that.
Also notice how everything needs to be quoted unless you specifically require the shell to do whitespace tokenization and wildcard expansion on the value of a token. Here's the scoop on that.
That may not be the most efficient way of doing it, but you could take all files, remove the extension, and the check if there isn't a directory with that name.
Like this (untested code):
for file in Mywiki/* ; do
if [ -f "$d" ]; then
dirname=$(getname "$d")
if [ ! -d "Mywiki/$dirname" ]; then
echo "$file"
fi
fi
done
To List all the files in current dir
list1=`ls -p | grep -v /`
To List all the files in current dir without extension
list2=`ls -p | grep -v / | sed 's/\.[a-z]*//g'`
To List all the directories in current dir
list3=`ls -d */ | sed -e "s/\///g"`
Now you can get the desired directory listing using intersection of list2 and list3. Intersection of two lists in Bash

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?

Creating a pathname to check a file doesn't exist there / Permission denied error

Hello from a Linux Bash newbie!
I have a list.txt containing a list of files which I want to copy to a destination($2). These are unique images but some of them have the same filename.
My plan is to loop through each line in the text file, with the copy to the destination occurring when the file is not there, and a mv rename happening when it is present.
The problem I am having is creating the pathname to check the file against. In the code below, I am taking the filename only from the pathname, and I want to add that to the destination ($2) with the "/" in between to check the file against.
When I run the program below I get "Permission Denied" at line 9 which is where I try and create the path.
for line in $(cat list.txt)
do
file=$[ basename $line ]
path=$[ $2$file ]
echo $path
if [ ! -f $path ];
then
echo cp $line $2
else
echo mv $line.DUPLICATE $2
fi
done
I am new to this so appreciate I may be missing something obvious but if anyone can offer any advice it would be much appreciated!
Submitting this since OP is new in BASH scripting no good answer has been posted yet.
DESTINATION="$2"
while read -r line; do
file="${line##*/}"
path="$2/$file"
[[ ! -f $path ]] && cp "$line" "$path" || mv "$line" "$path.DUP"
done < list.txt
Don't have logic for counting duplicates at present to keep things simple. (Which means code will take care of one dup entry) As an alternative you get uniq from list.txt beforehand to avoid the duplicate situation.
#anubhava: Your script looks good. Here is a small addition to it to work with several dupes.
It adds a numer to the $path.DUP name
UniqueMove()
{
COUNT=0
while [ -f "$1" ]
do
(( COUNT++ ))
mv -n "$1" "$2$COUNT"
done
}
while read -r line; do
file="${line##*/}"
path="$2/$file"
[[ ! -f $path ]] && cp "$line" "$path" || UniqueMove "$line" "$path.DUP"
done < list.txt

keeping track of a moving shell script

I hope someone can help me out. For the past month or so I have be learning the Bash... I have a program ( a simple language study program ) that I want to be able to install and run from a script.
I have a script that will create a new folder and move itself into it. The way I am doing it at the moment is below, although I have had problems with arrays that I am using later. I was wondering if there was a cleaner way of getting the new path to file name. Any help or insight would be greatly appreciated.
#!/bin/bash
echo "# path to me ---------------> ${0} "
echo "# parent path --------------> ${0%/*} "
echo "# my name ------------------> ${0##*/} "
if [[ ! -d ${0%/*}/SomeNewFolder ]] && [[ ! -d ${0%/*}/../SomeNewFolder ]]
then
mkdir ${0%/*}/SomeNewFolder
mv ${0} ${0%/*}/SomeNewFolder/${0##*/}
fi
echo ${0%/*}
newpath=$(echo "${0%/*}/SomeNewFolder")
echo $newpath
All the best, Ben
exit
For clarity, I would probably declare named variables for your common values instead of constantly reusing the ${0} array. It's also good practice to quote variables and strings.
The only major issue I saw, was running ./script.sh would make $0 equal just the filename, so I add "./" to the beginning in that case.
#!/bin/bash -u
ME="${0}"
if [[ ! "$ME" =~ /^\// ]]; then
ME="./$ME"
fi
PARENT="${ME%/*}"
FILENAME="${ME##*/}"
FOLDER="SomeNewFolder"
NEW="$PARENT/$FOLDER"
if [[ ! -d "$NEW" ]] && [[ "${PARENT%/*}" != "$FOLDER" ]]; then
mkdir "$NEW"
mv "$ME" "$NEW"
fi
echo "$PARENT"
echo "$NEW"
Well, you could do something like this to get an absolute path:
PARENTPATH=$( cd "$( dirname "$0" )" && pwd )
NEWPATH=${PARENTPATH}/SomeNewFolder
me="$0"
newdir=SomeNewFolder
if [[ $me =~ ^/ ]] ; then
full_path="$me"
else
full_path="$PWD/$me"
fi
full_path="${full_path//\/\.\///}" # prettify
path_to_me="${full_path%/*}"
parent_dir="${path_to_me##*/}"
if [ ! "$parent_dir" = "$newdir" ] ; then
mkdir -p "$path_to_me/$newdir"
mv -f "$full_path" "$path_to_me/$newdir/"
fi
Basically similar to what lunixbochs was doing, but with a few minor alterations
lower case variable names so as not to be confused with environment variables
crudely estimates absolute path
-f and -p becuase interactivity is never cool, and why not
Installing and setting up programs is more appropriately done from a make file. Granted, it seems intimidating at first, but the basics, such as what you want, are quite simple. For your project, you would ideally have three item:
your program
your run script
your makefile i.e. your installer
This breaks apart each of these different components, making each of them easier to manage. If you tar them together, you can move the tar file to a new computer and reinstall without any changes. Bash is a wonderful tool, but an installer it is not.
Sample make script below:
.PHONY: all clean
SCRIPT=yourScriptName.sh
SUBFOLDER=someFolder
all: $(SCRIPT)
$(SCRIPT): $(SUBFOLDER)
cp $(SCRIPT) $(SUBFOLDER)
$(SUBFOLDER):
mkdir $(SUBFOLDER)
clean:
-rm -f $(SUBFOLDER)/$(SCRIPT)
-rmdir $(SUBFOLDER)
IMPORTANT! make is whitespace sensitive! Those indents are tabs not four spaces.

Renaming a set of files to 001, 002,

I originally had a set of images of the form image_001.jpg, image_002.jpg, ...
I went through them and removed several. Now I'd like to rename the leftover files back to image_001.jpg, image_002.jpg, ...
Is there a Linux command that will do this neatly? I'm familiar with rename but can't see anything to order file names like this. I'm thinking that since ls *.jpg lists the files in order (with gaps), the solution would be to pass the output of that into a bash loop or something?
If I understand right, you have e.g. image_001.jpg, image_003.jpg, image_005.jpg, and you want to rename to image_001.jpg, image_002.jpg, image_003.jpg.
EDIT: This is modified to put the temp file in the current directory. As Stephan202 noted, this can make a significant difference if temp is on a different filesystem. To avoid hitting the temp file in the loop, it now goes through image*
i=1; temp=$(mktemp -p .); for file in image*
do
mv "$file" $temp;
mv $temp $(printf "image_%0.3d.jpg" $i)
i=$((i + 1))
done
A simple loop (test with echo, execute with mv):
I=1
for F in *; do
echo "$F" `printf image_%03d.jpg $I`
#mv "$F" `printf image_%03d.jpg $I` 2>/dev/null || true
I=$((I + 1))
done
(I added 2>/dev/null || true to suppress warnings about identical source and target files. If this is not to your liking, go with Matthew Flaschen's answer.)
Some good answers here already; but some rely on hiding errors which is not a good idea (that assumes mv will only error because of a condition that is expected - what about all the other reaons mv might error?).
Moreover, it can be done a little shorter and should be better quoted:
for file in *; do
printf -vsequenceImage 'image_%03d.jpg' "$((++i))"
[[ -e $sequenceImage ]] || \
mv "$file" "$sequenceImage"
done
Also note that you shouldn't capitalize your variables in bash scripts.
Try the following script:
numerate.sh
This code snipped should do the job:
./numerate.sh -d <your image folder> -b <start number> -L 3 -p image_ -s .jpg -o numerically -r
This does the reverse of what you are asking (taking files of the form *.jpg.001 and converting them to *.001.jpg), but can easily be modified for your purpose:
for file in *
do
if [[ "$file" =~ "(.*)\.([[:alpha:]]+)\.([[:digit:]]{3,})$" ]]
then
mv "${BASH_REMATCH[0]}" "${BASH_REMATCH[1]}.${BASH_REMATCH[3]}.${BASH_REMATCH[2]}"
fi
done
I was going to suggest something like the above using a for loop, an iterator, cut -f1 -d "_", then mv i i.iterator. It looks like it's already covered other ways, though.

Resources