unable to get the complete list of file matching the regular expression in shell script - shell

I have a directory with list of files abc_1.txt abc_2.txt.
I am have to parse the file name and do some processing over the file
#shellscript /tmp/log*
file_list=`ls $1`
for file_name in $file_list
do
# need to do some processing over the file name
echo $file_name
done
the script is not giving the proper output i.e script is not giving matching wildcard file name for the ls cmd.
the usage of the above script is shellscript /tmp/log*

Bash expands shellscript /tmp/log/abc* to a list of files names as input to your script. ls is not needed. Just iterate over the input. Try it like this
for f in $*
do
echo "Processing $f"
# do something on $f
done
http://www.cyberciti.biz/faq/bash-loop-over-file/ even gives you some more examples.

When you want filenames without the dir you can use
if [ ! -d "$*" ]; then
echo "Usage: $0 dirname"
exit 1
fi
cd "$*"
find . -type f | cut -c3- | while read file_name; do
echo "Found ${file_name}"
done

Just remove the wildcard character "*" since it matches all the files in the given path. Removing it will just pass the directory path and your script will print each file

Related

How to iterate over a directory and display only filename

I would want to iterate over contents of a directory and list only ordinary files.
The path of the directory is given as an user input. The script works if the input is current directory but not with others.
I am aware that this can be done using ls.. but i need to use a for .. in control structure.
#!/bin/bash
echo "Enter the path:"
read path
contents=$(ls $path)
for content in $contents
do
if [ -f $content ];
then
echo $content
fi
done
ls is only returning the file names, not including the path. You need to either:
Change your working directory to the path in question, or
Combine the path with the names for your -f test
Option #2 would just change:
if [ -f $content ];
to:
if [ -f "$path/$content" ];
Note that there are other issues here; ls may make changes to the output that break this, depending on wrapping. If you insist on using ls, you can at least make it (somewhat) safer with:
contents="$(command ls -1F "$path")"
You have two ways of doing this properly:
Either loop through the * pattern and test file type:
#!/usr/bin/env bash
echo "Enter the path:"
read -r path
for file in "$path/"*; do
if [ -f "$file" ]; then
echo "$file"
fi
done
Or using find to iterate a null delimited list of file-names:
#!/usr/bin/env bash
echo "Enter the path:"
read -r path
while IFS= read -r -d '' file; do
echo "$file"
done < <(
find "$path" -maxdepth 1 -type f -print0
)
The second way is preferred since it will properly handle files with special characters and offload the file-type check to the find command.
Use file, set to search for files (-type f) from $path directory:
find "$path" -type f
Here is what you could write:
#!/usr/bin/env bash
path=
while [[ ! $path ]]; do
read -p "Enter path: " path
done
for file in "$path"/*; do
[[ -f $file ]] && printf '%s\n' "$file"
done
If you want to traverse all the subdirectories recursively looking for files, you can use globstar:
shopt -s globstar
for file in "$path"/**; do
printf '%s\n' "$file"
done
In case you are looking for specific files based on one or more patterns or some other condition, you could use the find command to pick those files. See this post:
How to loop through file names returned by find?
Related
When to wrap quotes around a shell variable?
Why you shouldn't parse the output of ls
Is double square brackets [[ ]] preferable over single square brackets [ ] in Bash?

how to use diff command in conditional statements and in loops

I've created a script which recursively reads a path and prints its directory, and subdirectory into a csv file to write into excel columns. Now I want to modify this script such that first it will recursively read a path and print its content and then find the recursive difference of this path along with another path and I want to print if any directory is changed or not.
First code:
find path1 | while read file; do
if [[ -d "$file" ]]; then
echo $file > out.csv
else
echo...
fi
done
and second code is:
diff -rq $path1 $path2 | while read file1; do
if [[ "$file" != ]]; then
echo Changed >> out.csv
else
echo no >> out.csv
fi
done
Now I want to merge these codes such that fist it will recursively prints all content of path1 in csv file, then compare both paths and print changed if difference occur.
It's not overly clear what you're trying to accomplish. Your first part can be simplified to a single line:
find path1 -type d > out.csv
which only finds directories. (Note that the use of > overwrites out.csv, so previous contents don't matter (you made a mistake with that in your example)).
For you second part, you could do:
diff <( cd path1 && find . -type d ) <( cd path2 && find . -type d )
This grabs all the directories in path1, and all the directories in path2, and would output the difference in standard diff format...
I'm using process substition <( ) so bash treats the output as files. Also the cd to path1 or path2 before the find prevents find from outputting path1/ or path2/ in its results (otherwise every line would be different). You could do something similar with sed to strip off the path names.
If on the otherhand, your goal is to learn bash loops, you would do this as follows:
rm out.csv
find path | while read file; do
[[ -d $file ]] && echo $file >> out.csv
done
cat out.csv | while read file; do
[[ -e path2/${file#path1/} ]] || echo "file changed: $file"
done
Note that unlike the former example, this does not print any paths in path2 that are not in path1.
Here, I used ${file#path1/}, which strips path1/ from the front of each filename (as you're now looking in path2).
Hope this helps.

For files in directory, only echo filename (no path)

How do I go about echoing only the filename of a file if I iterate a directory with a for loop?
for filename in /home/user/*
do
echo $filename
done;
will pull the full path with the file name. I just want the file name.
If you want a native bash solution
for file in /home/user/*; do
echo "${file##*/}"
done
The above uses Parameter Expansion which is native to the shell and does not require a call to an external binary such as basename
However, might I suggest just using find
find /home/user -type f -printf "%f\n"
Just use basename:
echo `basename "$filename"`
The quotes are needed in case $filename contains e.g. spaces.
Use basename:
echo $(basename /foo/bar/stuff)
Another approach is to use ls when reading the file list within a directory so as to give you what you want, i.e. "just the file name/s". As opposed to reading the full file path and then extracting the "file name" component in the body of the for loop.
Example below that follows your original:
for filename in $(ls /home/user/)
do
echo $filename
done;
If you are running the script in the same directory as the files, then it simply becomes:
for filename in $(ls)
do
echo $filename
done;
You can either use what SiegeX said above or if you aren't interested in learning/using parameter expansion, you can use:
for filename in $(ls /home/user/);
do
echo $filename
done;
if you want filename only :
for file in /home/user/*; do
f=$(echo "${file##*/}");
filename=$(echo $f| cut -d'.' -f 1); #file has extension, it return only filename
echo $filename
done
for more information about cut command see here.

How can I manipulate file names using bash and sed?

I am trying to loop through all the files in a directory.
I want to do some stuff on each file (convert it to xml, not included in example), then write the file to a new directory structure.
for file in `find /home/devel/stuff/static/ -iname "*.pdf"`;
do
echo $file;
sed -e 's/static/changethis/' $file > newfile +".xml";
echo $newfile;
done
I want the results to be:
$file => /home/devel/stuff/static/2002/hello.txt
$newfile => /home/devel/stuff/changethis/2002/hello.txt.xml
How do I have to change my sed line?
If you need to rename multiple files, I would suggest to use rename command:
# remove "-n" after you verify it is what you need
rename -n 's/hello/hi/g' $(find /home/devel/stuff/static/ -type f)
or, if you don't have rename try this:
find /home/devel/stuff/static/ -type f | while read FILE
do
# modify line below to do what you need, then remove leading "echo"
echo mv $FILE $(echo $FILE | sed 's/hello/hi/g')
done
Are you trying to change the filename? Then
for file in /home/devel/stuff/static/*/*.txt
do
echo "Moving $file"
mv "$file" "${file/static/changethis}.xml"
done
Please make sure /home/devel/stuff/static/*/*.txt is what you want before using the script.
First, you have to create the name of the new file based on the name of the initial file. The obvious solution is:
newfile=${file/static/changethis}.xml
Second you have to make sure that the new directory exists or create it if not:
mkdir -p $(dirname $newfile)
Then you can do something with your file:
doSomething < $file > $newfile
I wouldn't do the for loop because of the possibility of overloading your command line. Command lines have a limited length, and if you overload it, it'll simply drop off the excess without giving you any warning. It might work if your find returns 100 file. It might work if it returns 1000 files, but it might fail if your find returns 1000 files and you'll never know.
The best way to handle this is to pipe the find into a while read statement as glenn jackman.
The sed command only works on STDIN and on files, but not on file names, so if you want to munge your file name, you'll have to do something like this:
$newname="$(echo $oldname | sed 's/old/new/')"
to get the new name of the file. The $() construct executes the command and puts the results of the command on STDOUT.
So, your script will look something like this:
find /home/devel/stuff/static/ -name "*.pdf" | while read $file
do
echo $file;
newfile="$(echo $file | sed -e 's/static/changethis/')"
newfile="$newfile.xml"
echo $newfile;
done
Now, since you're renaming the file directory, you'll have to make sure the directory exists before you do your move or copy:
find /home/devel/stuff/static/ -name "*.pdf" | while read $file
do
echo $file;
newfile="$(echo $file | sed -e 's/static/changethis/')"
newfile="$newfile.xml"
echo $newfile;
#Check for directory and create it if it doesn't exist
$dirname=$(dirname "$newfile")
if [ ! -d "$dirname" ]
then
mkdir -p "$dirname"
fi
#Directory now exists, so you can do the move
mv "$file" "$newfile"
done
Note the quotation marks to handle the case there's a space in the file name.
By the way, instead of doing this:
if [ ! -d "$dirname" ]
then
mkdir -p "$dirname"
fi
You can do this:
[ -d "$dirname"] || mkdir -p "$dirname"
The || means to execute the following command only if the test isn't true. Thus, if [ -d "$dirname" ] is a false statement (the directory doesn't exist), you run mkdir.
It's a fairly common shortcut when you see shell scripts.
find ... | while read file; do
newfile=$(basename "$file").xml;
do something to "$file" > "$somedir/$newfile"
done
OUTPUT="$(pwd)";
for file in `find . -iname "*.pdf"`;
do
echo $file;
cp $file $file.xml
echo "file created in directory = {$OUTPUT}"
done
This will create a new file with name whatyourfilename.xml, for hello.pdf the new file created would be hello.pdf.xml, basically it creates a new file with .xml appended at the end.
Remember the above script finds files in the directory /home/devel/stuff/static/ whose file names match the matcher string of the find command (in this case *.pdf), and copies it to your present working directory.
The find command in this particular script only finds files with filenames ending with .pdf If you wanted to run this script for files with file names ending with .txt, then you need to change the find command to this find /home/devel/stuff/static/ -iname "*.txt",
Once I wanted to remove trailing -min from my files. i.e. wanted alg-min.jpg to turn into alg.jpg. so after some struggle, managed to figure something like this:
for f in *; do echo $f; mv $f $(echo $f | sed 's/-min//g');done;
Hope this helps someone willing to REMOVE or SUBTITUDE some part of their file names.

Trying to cat files - unrecognized wildcard

I am trying to create a file that contains all of the code of an app. I have created a file called catlist.txt so that the files are added in the order I need them.
A snippet of my catlist.txt:
app/controllers/application_controller.rb
app/views/layouts/*
app/models/account.rb
app/controllers/accounts_controller.rb
app/views/accounts/*
When I run the command the files that are explicitly listed get added but the wildcard files do not.
cat catlist.txt|xargs cat > fullcode
I get
cat: app/views/layouts/*: No such file or directory
cat: app/views/accounts/*: No such file or directory
Can someone help me with this. If there is an easier method I am open to all suggestions.
Barb
Your problem is that xargs is not the shell, so the wildcard is being interpreted literally as an star. You'll need to have a shell to do the expansion for you like this:
cat catlist.txt | xargs -I % sh -c "cat %" > fullcode
Note that the * is not recursive in your data file. I assume that was what you meant. If you want the entries to be recursive, that's a little trickier and would need something more like DevNull's script, but that will require that you change your data file a bit to not include the stars.
Are you positive those directories exist?
The problem with doing a cat on a list like that (where you're using wildcards) is that the cat isn't recursive. It will only list the contents of that directory; not any subdirectories.
Here's what I would do:
#!/bin/bash.exe
output="out.txt"
if [ -f "$output" ]
then
rm $output
fi
for file in $(cat catlist.txt)
do
if [ -f "$file" ]
then
echo "$file is a file."
cat $file >> $output
elif [ -d "$file" ]
then
echo "$file is a directory."
find $file -type f -exec cat {} >> $output \;
else
echo "huh?"
fi
done
If the entry listed is a directory, it finds all files from that point on and cats them.
use a while read loop to read your file
while read -r file
do
if [ -f "$file" ]; then
yourcode "$file"
fi
# expand asterix
case "$file" in
*"*" )
for f in $file
do
yourcode "$f"
done
esac
done <"catlist.txt"

Resources