command line find first file in a directory - bash

My directory structure is as follows
Directory1\file1.jpg
\file2.jpg
\file3.jpg
Directory2\anotherfile1.jpg
\anotherfile2.jpg
\anotherfile3.jpg
Directory3\yetanotherfile1.jpg
\yetanotherfile2.jpg
\yetanotherfile3.jpg
I'm trying to use the command line in a bash shell on ubuntu to take the first file from each directory and rename it to the directory name and move it up one level so it sits alongside the directory.
In the above example:
file1.jpg would be renamed to Directory1.jpg and placed alongside the folder Directory1
anotherfile1.jpg would be renamed to Directory2.jpg and placed alongside the folder Directory2
yetanotherfile1.jpg would be renamed to Directory3.jpg and placed alongside the folder Directory3
I've tried using:
find . -name "*.jpg"
but it does not list the files in sequential order (I need the first file).
This line:
find . -name "*.jpg" -type f -exec ls "{}" +;
lists the files in the correct order but how do I pick just the first file in each directory and move it up one level?
Any help would be appreciated!
Edit: When I refer to the first file what I mean is each jpg is numbered from 0 to however many files in that folder - for example: file1, file2...... file34, file35 etc... Another thing to mention is the format of the files is random, so the numbering might start at 0 or 1a or 1b etc...

You can go inside each dir and run:
$ mv `ls | head -n 1` ..

If first means whatever the shell glob finds first (lexical, but probably affected by LC_COLLATE), then this should work:
for dir in */; do
for file in "$dir"*.jpg; do
echo mv "$file" "${file%/*}.jpg" # If it does what you want, remove the echo
break 1
done
done
Proof of concept:
$ mkdir dir{1,2,3} && touch dir{1,2,3}/file{1,2,3}.jpg
$ for dir in */; do for file in "$dir"*.jpg; do echo mv "$file" "${file%/*}.jpg"; break 1; done; done
mv dir1/file1.jpg dir1.jpg
mv dir2/file1.jpg dir2.jpg
mv dir3/file1.jpg dir3.jpg

Look for all first level directories, identify first file in this directory and then move it one level up
find . -type d \! -name . -prune | while read d; do
f=$(ls $d | head -1)
mv $d/$f .
done

Building on the top answer, here is a general use bash function that simply returns the first path that resolves to a file within the given directory:
getFirstFile() {
for dir in "$1"; do
for file in "$dir"*; do
if [ -f "$file" ]; then
echo "$file"
break 1
fi
done
done
}
Usage:
# don't forget the trailing slash
getFirstFile ~/documents/
NOTE: it will silently return nothing if you pass it an invalid path.

Related

move every file from every folder to specific folder

I am trying to write in shell script to run this..
I guess it requires for syntax or find syntax..
but I am stuck dealing with scan every folder..
I have tried with "find" -maxdepth 1 -name "*.jpg | mv " but failed...
for every jpg files in every dir (folder1, folder2, folder3...folder5...etc)
move files to target dir which is parent dir.
if file name duplicated, move to dup dir.
DIR tree looks like this
Something like
for f in folder*/*.jpg; do
if [ -e "$(basename "$f")" ]; then
mv "$f" dup/
else
mv "$f" .
fi
done
run from the parent directory. Just iterates over every jpg in the folder subdirectories, moving to one place or another depending on if a file with that name already exists or not.
Slightly more efficient bash version:
for f in folder*/*.jpg; do
if [[ -e ${f##*/} ]]; then
mv "$f" dup/
else
mv "$f" .
fi
done

Running Command Recursively in all Subdirectories

I have a folder which contains a depth of up to 3 layers of subdirectories, with images in the deepest subdirectory (for most images, this is just one level). I want to rename these images to include the name of the directory they are in as a prefix. For this, I need to be able to run a single command for each subdirectory in this tree of subdirectories.
This is my attempt:
DIRS=/home/arjung2/data_256_nodir/a/*
for dir in $DIRS
do
for f in $dir
do
echo "$dir"
echo "$f"
echo "$dir_$f"
mv "$f" "$dir_$f"
done
done
However, the first three echos prints out the same thing for each 1-level deep subdirectory (not all up to 3-level deep subdirectories as I desire), and gives me an error. An example output is the following:
/home/arjung2/data_256_nodir/a/airfield
/home/arjung2/data_256_nodir/a/airfield
/home/arjung2/data_256_nodir/a/airfield
mv: cannot move ‘/home/arjung2/data_256_nodir/a/airfield’ to a subdirectory of itself, ‘/home/arjung2/data_256_nodir/a/airfield/airfield’
Any ideas what I'm doing wrong? Any help will be much appreciated, thanks!!
Assuming all images can be identified with 'find' (e.g., by suffix), consider the following bash script:
#! /bin/bash
find . -type f -name '*.jpeg' -print0 | while read -d '' file ; do
d=${file%/*};
d1=${d##*/};
new_file="$d/${d1}_${file##*/}"
echo "Move: $file -> $new_file"
mv "$file" "$new_file"
done
It will move a/b/c.jpeg to a/b/b_c.jpeg, for every folder/file. Adjust (or remove) the -name as needed.
Say dir has the value /home/arjung2/data_256_nodir/a/airfield. In this case, the statement
for f in $dir
expands to
for f in /home/arjung2/data_256_nodir/a/airfield
which means that the inner loop will be executed exactly once, f taking the name /home/arjung2/data_256_nodir/a/airfield, which is the same as dir.
It would make more sense to iterate over the files within the directory:
for f in $dir/*

Bash script to scan sub-directories and copy contents of file to another file

I need to do a bash command that will look through every home directory on a system, and copy the contents of the .forward file to a single file, along with copying the name of the directory it just copied from. So for example the final file would be something like forwards.txt and listeings would be
/home/user1
user1#email.com
/home/user2
user2#email.com
I've used this to list them to screen.
find /home -name '*' | cat /home/*/.forward
and it will print out the forward in each file but I'm not getting it to prefix it with which home directory it came from. Would I need to use a loop to do this? I had this test loop,
#!/bin/bash
for i in /home/*
do
if [ -d $i ]
then
pwd >> /tmp/forwards.txt
cat /home/*/.forward >> /tmp/forwards.txt
fi
done
But it went through the four home directories on my test setup and the forwards.txt file had the following listed four times.
/tmp
user1#email.com
user2#email.com
user3#email.com
user3#email.com
Thanks.
There is corrected version of your script:
#!/bin/bash
for i in /home/*
do
if [ -f "$i/.forward" ]
then
echo "$i" >> /tmp/forwards.txt
cat "$i/.forward" >> /tmp/forwards.txt
fi
done
Some points:
we checks for presents of .forward file inside home directory instead of existence of home directory itself
on each iteration $i contains name of home directory (like /home/user1). So we use its value instead of output of pwd command which always returns current directory (it doesn't change in our case)
instead of /home/*/.forward we use "/home/$i/.forward" because * after substitution gives to us all directories, while we need only current
Another, shortest version of this script may looks like this:
find /home -type f -name '.forward' | while read F; do
dirname "$F" >>/tmp/forwards.txt
cat "$F" >>/tmp/forwards.txt
done
I would write
for fwd in /home/*/.forward; do
dirname "$fwd"
cat "$fwd"
done > forwards.txt
A one liner (corrected):
find /home -maxdepth 2 -name ".forward" -exec echo "{}" >> /tmp/forwards.txt \; -exec cat "{}" >> /tmp/forwards.txt \;
This will output:
/home/user1/.forward
a#a.a
b#b.b
/home/user2/.forward
a#b.c

Bash script copying certain type of file to another location

I was thinking if using a BASH script is possible without manually copying each file that is in this parent directory
"/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk
/System/Library/PrivateFrameworks"
So in this folder PrivateFrameworks, there are many subfolders and in each subfolder it consists of the file that I would like to copy it out to another location. So the structure of the path looks like this:
-PrivateFrameworks
-AccessibilityUI.framework
-AccessibilityUI <- copy this
-AccountSettings.framework
-AccountSettings <- copy this
I do not want the option of copying the entire content in the folder as there might be cases where the folders contain files which I do not want to copy. So the only way I thought of is to copy by the file extension. However as you can see, the files which I specified for copying does not have an extension(I think?). I am new to bash scripting so I am not familiar if this can be done with it.
To copy all files in or below the current directory that do not have extensions, use:
find . ! -name '*.*' -exec cp -t /your/destination/dir/ {} +
The find . command looks for all files in or below the current directory. The argument -name '*.*' would restrict that search to files that have extensions. By preceding it with a not (!), however, we get all files that do not have an extension. Then, -exec cp -t /your/destination/dir/ {} + tells find to copy those files to the destination.
To do the above starting in your directory with the long name, use:
find "/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS7.0.sdk/System/Library/PrivateFrameworks" ! -name '*.*' -exec cp -t /your/destination/dir/ {} +
UPDATE: The unix tag on this question has been removed and replaced with a OSX tag. That means we can't use the -t option on cp. The workaround is:
find . ! -name '*.*' -exec cp {} /your/destination/dir/ \;
This is less efficient because a new cp process is created for every file moved instead of once for all the files that fit on a command line. But, it will accomplish the same thing.
MORE: There are two variations of the -exec clause of a find command. In the first use above, the clause ended with {} + which tells find to fill up the end of command line with as many file names as will fit on the line.
Since OSX lacks cp -t, however, we have to put the file name in the middle of the command. So, we put {} where we want the file name and then, to signal to find where the end of the exec command is, we add a semicolon. There is a trick, though. Because bash would normally consume the semicolon itself rather than pass it on to find, we have to escape the semicolon with a backslash. That way bash gives it to the find command.
sh SCRIPT.sh copy-from-directory .extension copy-to-directory
FROM_DIR=$1
EXTENSION=$2
TO_DIR=$3
USAGE="""Usage: sh SCRIPT.sh copy-from-directory .extension copy-to-directory
- EXAMPLE: sh SCRIPT.sh PrivateFrameworks .framework .
- NOTE: 'copy-to-directory' argument is optional
"""
## print usage if less than 2 args
if [[ $# < 2 ]]; then echo "${USAGE}" && exit 1 ; fi
## set copy-to-dir default args
if [[ -z "$TO_DIR" ]] ; then TO_DIR=$PWD ; fi
## DO SOMETHING...
## find directories; find target file;
## copy target file to copy-to-dir if file exist
find $FROM_DIR -type d | while read DIR ; do
FILE_TO_COPY=$(echo $DIR | xargs basename | sed "s/$EXTENSION//")
if [[ -f $DIR/$FILE_TO_COPY ]] ; then
cp $DIR/$FILE_TO_COPY $TO_DIR
fi
done

How to copy and rename files in shell script

I have a folder "test" in it there is 20 other folder with different names like A,B ....(actually they are name of people not A, B...) I want to write a shell script that go to each folder like test/A and rename all the .c files with A[1,2..] and copy them to "test" folder. I started like this but I have no idea how to complete it!
#!/bin/sh
for file in `find test/* -name '*.c'`; do mv $file $*; done
Can you help me please?
This code should get you close. I tried to document exactly what I was doing.
It does rely on BASH and the GNU version of find to handle spaces in file names. I tested it on a directory fill of .DOC files, so you'll want to change the extension as well.
#!/bin/bash
V=1
SRC="."
DEST="/tmp"
#The last path we saw -- make it garbage, but not blank. (Or it will break the '[' test command
LPATH="/////"
#Let us find the files we want
find $SRC -iname "*.doc" -print0 | while read -d $'\0' i
do
echo "We found the file name... $i";
#Now, we rip off the off just the file name.
FNAME=$(basename "$i" .doc)
echo "And the basename is $FNAME";
#Now we get the last chunk of the directory
ZPATH=$(dirname "$i" | awk -F'/' '{ print $NF}' )
echo "And the last chunk of the path is... $ZPATH"
# If we are down a new path, then reset our counter.
if [ $LPATH == $ZPATH ]; then
V=1
fi;
LPATH=$ZPATH
# Eat the error message
mkdir $DEST/$ZPATH 2> /dev/null
echo cp \"$i\" \"$DEST/${ZPATH}/${FNAME}${V}\"
cp "$i" "$DEST/${ZPATH}/${FNAME}${V}"
done
#!/bin/bash
## Find folders under test. This assumes you are already where test exists OR give PATH before "test"
folders="$(find test -maxdepth 1 -type d)"
## Look into each folder in $folders and find folder[0-9]*.c file n move them to test folder, right?
for folder in $folders;
do
##Find folder-named-.c files.
leaf_folder="${folder##*/}"
folder_named_c_files="$(find $folder -type f -name "*.c" | grep "${leaf_folder}[0-9]")"
## Move these folder_named_c_files to test folder. basename will hold just the file name.
## Don't know as you didn't mention what name the file to rename to, so tweak mv command acc..
for file in $folder_named_c_files; do basename=$file; mv $file test/$basename; done
done

Resources