Bash create while loop to remove all files within a directory - bash

Trying to create a while loop to remove 8 files from a specified test folder. I keep getting the error no such file or directory even though I am positive I am in the right folder because I can use the ls command to see the files... Anyways here is what I have
#!/bin/bash
var=(`ls ~/Random/Unit1/Test`)
x=${#var[#]}
i=0
while [ $i -lt $x ] ; do
rm $var # this line is incorrect and needs changing
((i++))
done

var is an array variable, right now you're accessing it as a scalar, which in bash returns the first value, so you remove the first file and then try to remove it again once for every file in the directory. If you want to remove every file you need to get the value of the array at every index, so in the loop you would get the nth value in the array, ie
rm ${var[$i]}

There are a couple of problems with your script, the first one is that you should cd to the folder from where you want to remove the files, you can use pushd and popd for it. Second, you should enclose the var variable with double quotes. Also, as stated in #redball's answer, you are accessing an array, you have to use array notation on it.
#!/bin/bash
DIRECTORY=~/Random/Unit1/Test
var=(`ls $DIRECTORY`)
x=${#var[#]}
i=0
# Saves current directory and change it to the one pointed by "$DIRECTORY"
push "$DIRECTORY"
while [ $i -lt $x ] ; do
rm "${var[$i]}"
((i++))
done
# Restores the previously saved directory
popd

Related

POSIX array and loop

I make an array of files as follows, and then "source" those files. I am also trying to make a script as close to POSIX possible so I don't have issues running it in different shells.
set -- path alias function
for file in "${#}"; do
[ -r "${SELF_PATH_DIR}.${file}" ] && [ -f "${SELF_PATH_DIR}.${file}" ] && . "${SELF_PATH_DIR}.${file}";
done
It works, but I don't like the fact that I have to specify ${SELF_PATH_DIR}. many times, so that the string takes the real path to the files (/Users/karlsd/dotfiles/.path, etc.).
Is there any way to make it simpler? For example, to add /Users/karlsd/dotfiles/. to each item before the loop?
Just assign "${SELF_PATH_DIR}.${file}" to a temporary variable or modify the same variable.
By the way: If the loop is the only spot where you are using set --/$# then you can iterate directly over the list:
for file in path alias function; do
file="${SELF_PATH_DIR}.${file}"
[ -r "$file" ] && [ -f "$file" ] && . "$file";
done
You actually have a potentially larger problem than a mere repetition of the variables: If one of the entries in $# happens to be the absolute path to some file, your code would break.
I would use realpath inside the loop:
absfile=$(realpath "$file")
[ -r "$absfile" ] && [ -f "$absfile" ] && . "$absfile"
Of course this would silently skip "buggy" entries of file, for instance if the name denotes a directory instead of a plain file or those you have no read access. Unless this is the desired behaviour, I would omit the tests and just source the file. If things aren't right, you get at least an explicit error message.

Failure to move gzipped file

I'm attempting to write a program that moves zipped files that arrive in a directory, unzips them and then outputs the contents.
#!/bin/bash
shopt -s extglob
echo "Press [CTRL+C] to stop.."
#begin loop
while :
do
#search folder test_source for files and append names to array
queue+=($(ls /home/ec2-user/glutton/test_source | egrep 'test[0-9]{1}.gz'))
for i in $queue; do
#move file in test_source to temp folder
mv /home/ec2-user/glutton/test_source/${queue[i]} /home/ec2-user/glutton/temp
#unzip file
gunzip /home/ec2-user/glutton/temp/${queue[i]}
#add new file name to variable unzipped
unzipped=($(ls /home/ec2-user/glutton/temp | egrep 'test[0-9]{1}'))
cat temp/$unzipped
#Test for successful run
exit_status=$?
if [ $exit_status -eq 1 ]; then
#If fail move file to bank and output
mv /home/ec2-user/glutton/temp/$unzipped /home/ec2-user/glutton/logs/bank
echo "Error! $unzipped moved to /home/ec2-user/glutton/logs/bank"
#write to error.log
echo "moved ${queue[i]} to bank due to error" >> /home/ec2-user/glutton/logs/error.log
else
#If success delete file
rm /home/ec2-user/glutton/temp/$unzipped
fi
#wipe variable
unset unzipped
i=$i+1
done
#wipe array
unset queue
i=0
#go to top of loop
done
This has worked pretty well up until I added the unzipping feature and now my program outputs this error when attempting to move the .gz file:
./glutton.sh: line 11: test0.gz: syntax error: invalid arithmetic operator (error token is ".gz")
When I run the first part of my script in the command line it seems to work perfectly, but doesn't when I run it on its own, I'm pretty confused.
Your main issue is that when you iterate an array like you are doing, you get the first item of the array, not the index. So in your case, $i is not a number, it is the filename (i.e. test1.gz) and it will only see the first file. The cleanest way I have seen to iterate the items in an array would be for i in "${arrayName[#]}".
Also, using '{1}' in your regex is redundant, the character class will already match only 1 character if you don't specify a modifier.
It shouldn't matter depending on the contents of your 'temp' folder, but I would be more specific on your egreps too, if you add -x then it will have to match the whole string. As it is currently, a file called 'not_a_test1' would match.

Bash - while inside for loop not exiting

I'm a beginner getting started with bash scripting.
I have 10 directories in current working directory dir1-dir10 + script.sh + a file called "tocopyfile".
Dir1-10 are empty.
tocopyfile is a test text file to be used for the purpose of my training
script.sh contains the following code:
dir=`pwd`
i="0"
for directory in `ls $dir`;do
while [ $i -le 10 ]
do
cp tocopyfile $directory/file$i &
i=$[$i+1]
done
done
The script should copy 10 copies of the file "tocopyfile" to every dir (dir1-10) in the naming convention file#. The problem is that ones the script is exists after the first directory without executing the while loop to the remaining remaining dirs.
Can someone explain what I'm doing wrong please?
Help is greatly appreciated.
The immediate issue is that you need to reset the value of i for each iteration of the outer loop.
for directory in `ls $dir`; do # No! but more on that in a moment
i=0
while [ $i -le 10 ]
There are a few other issues with your code.
dir=$(pwd) is almost always pointless; bash already provides a variable PWD containing the name of the current working directory. You don't actually need this, though; you can simply use ./*/ to expand to a list of directories in the current working directory.
Never use the output of ls in a script.
$[...] is obsolete syntax; use $((...)) instead.
Cleaning your code up a bit, we get
for directory in ./*/; do
i=0
while [ "$i" -le 10 ]; do
cp tocopyfile "$directory/file$i" &
i=$((i+1))
done
done
You need to initialize $i inside the for loop, such that $i == 0 upon each iteration of your while:
dir=`pwd`
for directory in `ls $dir`;do
i="0" # <===== notice the change here
while [ $i -le 10 ]
do
cp tocopyfile $directory/file$i &
i=$[$i+1]
done
done
Other things you might want to change:
Double-quote all your variables (in the event they have spaces in them).
Use $() instead of the long-deprecated back-tick syntax.
Use $(()) instead of the deprecated $[] syntax.
Tidy up your indentation.

move files based on filename length

I want to move some files in a directory, using their filename length as the criteria.
For example, I want to move any files longer that 10 characters.
I assumed I need an if loop in bash script, but I'm not sure how to proceed.
use this template
for f in *; do if [ ${#f} -gt 10 ]; then echo $f; fi; done
replace echo with your mv command.
note that directories will be in the list too.

Why can't I pass a directory as an argument to a for loop in bash?

I have a simple bash script, simple.sh, as follows:
#/usr/local/bin/bash
for i in $1
do
echo The current file is $i
done
When I run it with the following argument:
./simple.sh /home/test/*
it would only print and list out the first file located in the directory.
However, if I change my simple.sh to:
#/usr/local/bin/bash
DIR=/home/test/*
for i in $DIR
do
echo The current file is $i
done
it would correctly print out the files within the directory. Can someone help explain why the argument being passed is not showing the same result?
If you take "$1", it is the first file/directory, which is possible!
You should do it in this way:
for i in "$#"
do
echo The current file is ${i}
done
If you execute it with:
./simple.sh *
They list you all files of the actual dictionary
"$1" is alphabetical the first file/directory of your current directory, and in the for loop, the value of "i" would be e.g. a1.sh and then they would go out of the for loop!
If you do:
DIR=/home/<s.th.>/*
you save the value of all files/directories in DIR!
This is as portable as it gets, has no useless forks to ls and runs with a minimum of CPU cycles wasted:
#!/bin/sh
cd $1
for i in *; do
echo The current file is "$i"
done
Run as ./simple.sh /home/test
Your script does not receive "/home/test/*" as an argument; the shell expands the patter to the list of files that match, and your shell receives multiple arguments, one per matching file. Quoting the argument will work:
./simple.sh "/home/test/*"
Your change to using DIR=/home/test/* did what you expected because filename generation is not performed on the RHS of a variable assignment. When you left $DIR unquoted in the for loop, the pattern was expanded to the list of matching files.
How about list the file manully instead of using *:
#/usr/local/bin/bash
for i in $(ls $1)
do
echo The current file is $i
done
and type
./simple.sh /home/test/

Resources