Sorting folders with bash - shell

My root folder structure is like this:
My Family - Holiday
My Birthday[15.11]
Name-Name[1]
Name1
Name2
...
Now, I want every folder which contains a .info file to move to another directory.
Here's my code:
#!/bin/sh
for folder in *
do
echo $folder
if [ -e "$folder/*.info" ]
then
echo $folder
mv $folder ./Finished
fi
done
The echoes are only for testing.
I have found that every time a non-escaped character is in the name that the if is failing. How can I fix this?

Bash wants you to quote your variables.
#!/bin/bash
if [[ "$1" = "-v" ]]; then
Verbose=true
vopt="-v"
shift
else
Verbose=false
vopt=""
fi
for folder in *; do
$Verbose && printf "%s" "$folder"
if [[ -e "$folder/*.info" ]]; then
if mv "$vopt" "$folder" ./Finished/; then
$Verbose && echo -n " ... done"
fi
fi
$Verbose && echo ""
done
Note that it's a good idea to end your target directory with a slash. That way, if for some reason the Finished directory disappears, you'll get an error rather than silently renaming the first $folder to Finished, then moving all the other matches into the first match.
Note also that I'm using printf for some of the debugging output just in case one of your $folders starts with a hyphen.
UPDATE #1: you now have debugging controlled with the -v option.
UPDATE #2: I just realized that you are checking for the existence of *.info, literally. Note:
ghoti#pc ~$ mkdir foo
ghoti#pc ~$ touch foo/\*.info
ghoti#pc ~$ ls -la foo
total 0
-rw-r--r-- 1 ghoti ghoti 0 8 Aug 07:45 *.info
drwxr-xr-x 3 ghoti ghoti 102 8 Aug 07:45 .
drwx------+ 10 ghoti ghoti 340 8 Aug 07:44 ..
ghoti#pc ~$ [[ -e "foo/*.info" ]] && echo yes
yes
ghoti#pc ~$ mv foo/\*.info foo/bar.info
ghoti#pc ~$ [[ -e "foo/*.info" ]] && echo yes
ghoti#pc ~$
If what you really want to find is "any file ending in .info", then [[ -e is not the way to go. Pls confirm before I work more on this answer. :)
UPDATE #3:
Here's a version that finds moves your folder if the folder conains any .info file. Note that this does not grep the output of ls.
[ghoti#pc ~/tmp1]$ cat doit
#!/bin/bash
if [[ "$1" = "-v" ]]; then
Verbose=true
vopt="-v"
shift
else
Verbose=false
vopt=""
fi
for folder in *; do
infofile="$(
if [[ -d "$folder" ]]; then
cd "$folder"
for file in *.info; do
if [[ -f "$file" ]]; then
echo "$file"
break
fi
done
fi
)"
if [[ -f "$folder/$infofile" ]]; then
mv "$vopt" "$folder" ./Finished/
elif $Verbose; then
printf "%s ... skipped\n" "$folder"
fi
done
[ghoti#pc ~/tmp1]$ find . -print
.
./doit
./baz
./foo
./foo/bar.info
./Finished
[ghoti#pc ~/tmp1]$ ./doit -v
Finished ... skipped
baz ... skipped
doit ... skipped
foo -> ./Finished/foo
[ghoti#pc ~/tmp1]$

The primary issue is that you cannot use test -e *.info. You can parse the output of ls to check for the file. There are issues with parsing ls, but they are much less significant than many people make them out to be. If you do not allow newlines in filenames, the following should work:
#!/bin/sh
for dir in *; do
if ls -f "$dir" | grep -q '\.info$'; then
mv "$dir" ./Finished
fi
done
Do note that it is essential that no filenames have an embedded newline, since this will incorrectly identify a file named foo.info\nbar as if there were a single file named foo.info. In reality, this is unlikely to be a significant issue. If this is an issue, you will not want to use the shell for this, although you could do:
#!/bin/sh
for dir in *; do
for f in "$dir"/*; do
case "$f" in
*.info) mv "$dir" ./Finished; break;;
esac
done
done

Try this:
PATH_TO_FOLDER="/YOUR_FOLDER"
for f in `ls $PATH_TO_FOLDER`;
do
# Check filename
if [ $(echo $f | grep -Ec ".info$") -ne 1 ]
then
echo "We don't care"
else
## move the file
fi
done

Related

About accessing file (names) in specific directory and changing it

I'm writing code to exchange the upper and lower alphabet of file's name in certain directory.
If directory is unable to access, it shows err message.
#!/bin/bash
if [ $# -eq 0 ];then
path=$(pwd)
for a in "$path"/*
do
mv "$i" "`echo $i | tr '[:upper:][:lower:]' '[:lower:][:upper:]'`"
done
else
if [ ! -d "$1" ];then
echo "Unable to access directory!"
else
for i in "$1"/*
do
mv "$i" "`echo $i | tr '[:upper:][:lower:]' '[:lower:][:upper:]'`"
done
fi
fi
The problem is that, when I echo $i, it doesn't express ONLY filename, but filename with directory!
So, when I try to mv the file, not only file's name is changed but ALSO directory's name is changed so I can't mv the file.
Like this:
mv: cannot move 'test3/Ipad.txt' to 'TEST3/iPAD.TXT': No such file or directory
mv: cannot move 'test3/iPhone' to 'TEST3/IpHONE': No such file or directory
mv: cannot move 'test3/macOS' to 'TEST3/MACos': No such file or directory
how can I change the file names in certain directory?
Any help would be appreciable and thanks in advance.
I'd use a single loop for handling both cases.
#! /bin/sh -
case $# in
( 0 ) path=. ;;
( * ) path=$1
esac
for fpath in "$path"/*; do
echo mv -- "$fpath" "${fpath%/*}/$(
printf '%s\n' "${fpath##*/}" |
tr '[a-zA-Z]' '[A-Za-z]')"
done
If the path given by user is not present or accessible, this'll error out without causing any harm. But if you insist on handling that yourself, add a check before loop, or, use nullglob with bash and the script will exit silently on such an occasion.
Also note that the possibility that files having neither upper nor lower case letters in their names may exist is ignored here. Nothing will happen to them but mv will complain that source and target are the same.
You may want to cd to the directory first, then work on filenames in the directory:
#!/bin/bash
if [ $# -eq 0 ]; then
path=$(pwd)
cd "$path"
for a in *
do
mv "$i" "`echo $i | tr '[:upper:][:lower:]' '[:lower:][:upper:]'`"
done
cd -
else
if [ ! -d "$1" ]; then
echo "Unable to access directory!"
else
cd "$1"
for i in *
do
mv "$i" "`echo $i | tr '[:upper:][:lower:]' '[:lower:][:upper:]'`"
done
cd -
fi
fi

can't read all file lines in bash pipeline

I searched and couldn't find anything, maybe I can't understand the problem properly.
I have a bash function who read files in current dir and sub dir's, I'm trying to arrange the text and analyze the data but somehow I'm losing lines if I'm using pipeline.
the code:
function recursiveFindReq {
for file in *.request; do
if [[ -f "$file" ]]; then
echo handling "$file"
echo ---------------with pipe-----------------------
cat "$file" | while read -a line; do
if (( ${#line} > 1 )); then
echo ${line[*]}
fi
done
echo ----------------without pipe----------------------
cat "$file"
echo
echo num of lines: `cat "$file" | wc -l`
echo --------------------------------------
fi
done
for dir in ./*; do
if [[ -d "$dir" ]]; then
echo cd to $dir
cd "$dir"
recursiveFindReq "$1"
cd ..
fi
done
}
the output is:
losing lines even when they meet requirements
I marked with 2 red arrows the place I'm losing info

Check if a file exists inside a "Variable" Path

I am trying to find if a file exist in an iPhone application directory
Unfortunately, apps directory differs from a device to another
On my device, i use the following command to see if the file exists:
if [[ -f "/var/mobile/Applications/D0D2B991-3CDA-457B-9187-1F02A84FF3AB/AppName.app/filename.txt" ]]; then
echo "The File Exists";
else
echo "The File Does Not Exist";
fi
I want a command that would automatically search if the file exist without the need to specify the "variable" name inside the path.
I tried this:
if [[ -f "/var/mobile/Applications/*/AppName.app/filename.txt" ]]; then
echo "The File Exists";
else
echo "The File Does Not Exist";
fi
But no luck, it didn't find the file,
Maybe because i have 2 path of /var/mobile/Applications/*/AppName.app/ since i have cloned the app.
I would like to get a way to be able to find if the file filename.txt exists inside any folder named AppName.app inside this directory /var/mobile/Applications/*/
You can do this as follows:
[[ $(find /var/mobile/Applications/*/AppName.app/ -name filename.txt -print -quit | wc -l) -gt 0 ]] && echo "The File Exists" || echo "The File Does Not Exist"
The -f test can only take one argument. You would need to put it in a loop to check if some glob exists and its matches some regular file, i.e.
shopt -s nullglob
found=
for file in /var/mobile/Applications/*/AppName.app/filename.txt; do
[[ -f $file ]] && found=: && break
done
[[ -n $found ]] && echo "The File Exists" || echo "The File Does Not Exist"
If you're not sure specifically where the file is located you can use find, doing something like below which will exit early if found. (should work for gnu find, haven't tested on bsd)
if [[ -f $(find /some_root_directory -type f -name 'filename.txt' -print -quit) ]]; then
echo "The File Exists"
else
echo "The File Does Not Exist"
fi
# if a glob matches nothing, remove it instead of leaving the literal glob
shopt -s nullglob
# stick all matches in an array
files=( /var/mobile/Applications/*/AppName.app/filename.txt )
case "${#files[#]}" in
0 ) echo "Sorry, no such file." ;;
1 ) echo "The file exists: ${files[0]}" ;;
* ) echo "There are multiple files matching this pattern: ${files[*]}" ;;
esac
I like this technique for the purpose:
if find /var/mobile/Applications/*/AppName.app/ -name filename.txt -print -quit | grep -q .; then
echo "The File Exists"
else
echo "The File Does Not Exist"
fi
This has some advantages over this form:
[[ $(find ..... -print -quit | wc -l) -gt 0 ]]
Because:
It doesn't need a $() subshell
It doesn't need to count lines with wc
It doesn't need to compare numbers with the -gt operator
It doesn't need to be inside a [[ ... ]]
Basically it's a find ... | grep -q . versus [[ $(find ... | wc -l) -gt 0 ]]
Or find ... | grep -q . versus [[ -f $(find ...) ]]

cd into directory in while loop doesn't work

I've the following script:
#!/bin/bash
ls -1 | while read d
do
[[ -f "$d" ]] && continue
echo $d
cd $d
done
Problem is that each cd says "[path]: No such file or directory", why?
Folder exists because I list it ...
I see two problems in your code:
You do not test for directories.
Once you cd in the dir, you stay there.
Please try this:
#!/bin/bash
ls -1 | while read d
do
test -d "$d" || continue
echo $d
(cd $d ; echo "In ${PWD}")
done
You shouldn't use ls like that.
#!/bin/bash
for d in */
do
[[ ! -d "$d" ]] && continue
echo "$d"
cd "$d"
# do something
cd "$OLDPWD"
done
Don't know what you are trying to achive.
Do you want to do some kind of recursive dic traversal ?
Here's ho I would do it:
#!/bin/bash
CWD="$(pwd)" #save starting directory
ls -1 | while read d
do
[[ ! -d "$d" ]] && continue
cd "$d"
echo "Changed to directory" $d
#Now you can do someting in the sub-directory
cd "$CWD" #Change back to old directory
done
The problem is you change the working directory in your script. This means that the second cd will be executed in the subdirectory you entered in the previous cd.
dir=`pwd`
ls -1 | while read d
do
[[ -f "$d" ]] && continue
cd "$dir"
echo $d
cd $d
#....
done

Shell programming, looping through files

I am trying to loop through files in a specified directory. But I can't seem to figure out the logic. I am looping through each file and asking if they want to delete that file.
#!/bin/bash
dirpath=$1
y=y
Y=Y
echo "changing directory '$dirpath' `cd $dirpath`"
for f in $1/*
do
#####################################
if test -f `ls -1 $1`
then
echo -n "remove file '$f' `ls -1` ?"
read answer
##########################
if test $answer = $y || test $answer = $Y
then
echo "Processing $f file..."
echo `rm $f`
echo "file '$f' deleted "
else
echo "file '$f' not removed"
fi#2nd if loop
############################
else
echo 'not a file'
fi#1st if loop
#######################################
done
Your code seems much more complicated that it should be. Does this fulfill your needs or are you doing some shell practice?
rm -iv DIRECTORY/*
There's no need for ls, you already have the filename. Change this:
if test -f `ls -1 $1`
to:
if test -f "$f"
Why are you using echo and backticks here? Change
echo `rm $f`
to:
rm "$f"
Here's another place you're using backticks unnecessarily. Change this:
echo "changing directory '$dirpath' `cd $dirpath`"
to:
echo "changing directory '$dirpath'"
cd "$dirpath"
Always quote variables that contain filenames.
You can have rm do the "asking" for you via its -i flag to prompt user before removal. I am assuming you want to consider only files, not directories, and not recurse any sub-directories.
#!/bin/bash
for f in $1/* ; do
if [ -f $f ] ; then
rm -i $f ;
fi
done
Without the error, can't really help, but it could be written like this, not as verbose though
rm -i *
If $1 is a relative path, then once you've cd'd into $1, the wildcard in your for loop will be meaningless. I'd recommend something more like
cd $1
for f in *; do
...
done
Since it will accept both relative and absolute paths.
Moreover, the arguments to the first test are wrong. Each time through the loop, $f will hold one filename, so your test should be like
if (test -f $f); then
You also repeat this in your echo arguments.
The following does basically what you want, with only slight modifications from your script.
#!/bin/bash
dirpath=$1
y=y
Y=Y
echo "changing directory '$dirpath' `cd $dirpath`"
for f in ./*; do
if (test -f $f); then
echo -n "remove file '$f' ?"
read answer
if (test $answer == $y) || (test $answer == $Y); then
echo "Processing $f file..."
rm $f
echo "file '$f' deleted "
else
echo "file '$f' not removed"
fi
else
echo 'not a file'
fi
done

Resources