I have this bash code:
#!/bin/bash
ls "$SOURCE" > cplist.txt
cat cplist.txt | while read FILE; do
echo "Would you like to copy $FILE? y/n"
read ANSWER
if [ "$ANSWER" = "y" ]; then
cp -v "$FILE" ./Destination;
else
echo "File NOT coppied"
fi
done
The idea is to make a list of all files in a folder, then ask the user each file they want to copy to the destination folder. Currently this script wont allow user input after: "would you like to copy $file? y/n". I cant work out why as I'm very new to this kind of thing. Any ideas?
Thanks in advance!
You've run into problems as you're using two separate calls to read but you're making things more complicated than they need to be. Try something like this:
#!/bin/bash
for file in "$source"/*; do
echo "Would you like to copy $file? y/n"
read -r answer
if [ "$answer" = "y" ]; then
cp -v "$file" ./Destination;
else
echo "File NOT copied"
fi
done
Instead of parsing ls (which is generally considered a cardinal sin), this uses the shell's built-in glob expansion to match everything in the directory $source. I have added the -r switch to read, which is a good idea in general. I have also made your variable names lowercase. Uppercase variables should be reserved for built-in variables such as $PATH, which you certainly don't want to ever accidentally overwrite.
You make the cplist.txt to the remaining part's standard input where the read tries to read from.
Related
This question already has an answer here:
Script fails with spaces in directory names
(1 answer)
Closed 6 years ago.
I found this script online that I'm trying to edit, but as I'm testing it I can see that it will spit out a bunch of errors for all the files I have with spaces. This is the kind of error log I get on the terminal window:
Skipping 1-03 as ./mp3/basename "$input_file" .wav.mp3 exists.
Skipping The as ./mp3/basename "$input_file" .wav.mp3 exists.
Skipping power, as ./mp3/basename "$input_file" .wav.mp3 exists.
And this is the script:
#!/bin/bash
# Title: wav_to_mp3.sh
# Purpose: Converts all WAV files present in the folder to MP3
# Author: Karthic Raghupathi, IVR Technology Group LLC
# Last Revised: 2014.01.28
# references
sox="/usr/local/bin/sox"
sox_options="-S"
# variables
source_folder="${1:-.}"
destination_folder="${source_folder}/mp3"
environment="${2:-DEVELOPMENT}"
# check to see if an environment flag was supplied
if [ $environment = "PRODUCTION" ] || [ $environment = "production" ]; then
sox="/usr/bin/sox"
environment="PRODUCTION"
fi
# print all params so user can see
clear
echo "Script operating using the following settings and parameters....."
echo ""
echo "which SoX: ${sox}"
echo "SoX options: ${sox_options}"
echo "Environment: ${environment}"
echo "Source: ${source_folder}"
echo "Destination: ${destination_folder}"
echo ""
read -e -p "Do you wish to proceed? (y/n) : " confirm
if [ $confirm = "N" ] || [ $confirm = "n" ]; then
exit
fi
# create destination if it does not exist
if [ ! -d "${destination_folder}" ]; then
mkdir -p "${destination_folder}"
fi
# loop through all files in folder and convert them to
for input_file in $(ls -1 $1 | grep .wav)
do
name_part=`basename "$input_file" .wav`
output_file="$name_part.mp3"
# create mp3 if file does not exist
if [ ! -f "$destination_folder/$output_file" ]; then
$sox $sox_options "${source_folder}/$input_file" "$destination_folder/$output_file"
else
echo "Skipping ${input_file} as $destination_folder/$output_file exists."
fi
done
I know I'm supposed to make it escape the space characters, but I can't figure out how. I tried changing some quotes here and there but I'm just breaking it.
BTW, if anyone would be so kind as to link a good tutorial for learning how to make bash scripts on Mac OS (or Unix), that would be much appreciated. I already know a bit of web programming so I'm not a complete n00b, but still, I'm having trouble creating very simple scripts and I would like to learn independently without constantly bugging the internet for help :)
This is wrong:
for input_file in $(ls -1 $1 | grep .wav)
See here why. Also, inside $1, try this to see that filenames with spaces give trouble:
for i in $(ls -1 | grep wav); do echo $i; done
Try this instead:
for input_file in $1/*.wav
You can escape spaces by inserting a backslash character before the space.
Change:
This file name
To:
This\ file\ name
It might be an idea to write a function to do this for you, iterate through each character in a string and adding a \ caracter before any spaces. that way you don't need to worry about pre-formatting the file names and escaping each individual space - just run the file name through the function and capture the result.
I'm working on a shell script that will copy all the files from the command line to a directory. If the command line arguments contain any duplicate files, I want to prompt the user to either overwrite the existing file (it should now be in the directory), don't copy it, or rename it and then copy it.
What is the best way to approach this problem?
Here's some pseudocode of what I'm thinking:
for var in "$#"
do
for file in "$dirName" #unsure about this syntax too
do
if [ file or directory with name "$fileName" exists]; then
prompt user with options #i can handle this part :)
else
mv $var $dirName
fi
done
done
You are overcomplicating things.
for var in "$#"
do
if [ -e "$dirname/$var" ]; then
prompt user with options #i can handle this part :)
else
mv "$var" "$dirName"
fi
done
Make sure you use adequate quoting everywhere, by the way. Variables which contain file names should basically always be double quoted.
So, I wrote some a BASH shell script for renaming image files downloaded from deviant art so the artist name comes first, then the name of the piece of art. (for those not familiar with dA, the system names downloadable image files as imageTitle_by_ArtistsName.extention, which makes it hard to organize images quickly). It works... but it seems so clunky. Is there a more elegant way to handle this?
The code:
#!/bin/bash
#############################
# A short script for renaming
#Deviant Art files
#############################
echo "Please enter your image directory: "
read NewDir
echo "Please enter your destination directory: "
read DestinationDir
mkdir $DestinationDir
cd $NewDir
ls>>NamePile
ListOfFiles=`cat NamePile`
for x in $ListOfFiles
do
#Pull in the file Names
FileNameVar=$x
#Get the file types
FileType='.'${FileNameVar#*.}
#Chop the Artists name
ArtistsName=${FileNameVar%%.*}
ArtistsName=${ArtistsName##*_by_}
#Chop the pieces name
ImageName=${FileNameVar%%.*}
ImageName=${ImageName%%_by_*}
#Reassemble the New Name
NewFileName=$ArtistsName" "$ImageName$FileType
cp $x ../$DestinationDir/"$NewFileName"
done
rm NamePile
#######################################
You can simply the loop greatly by using regular expression matching.
for file in *; do # Don't parse the output of ls; use pattern matching
[[ $file =~ (.*)_by_(.*)\.(.*) ]] || continue
imageName="${BASH_REMATCH[1]}"
artistsName="${BASH_REMATCH[2]}"
fileType="${BASH_REMATCH[3]}"
cp "$file" "../$DestinationDir/$artistsName $imageName.$fileType"
done
When writing shell scripts, it's usually easiest to simply make use of existing Linux utilities. In this case, for example, sed can do most of the heavy lifting for you. This probably isn't the most robust code snippet, but you get the idea:
for file in *.jpg; do
newFile=`echo $file | sed 's/\(.*\)_by_\(.*\)\(\..*\)/\2_\1\3/g'`
mv $file $newFile
done
Sometimes I need to rename some amount of files, such as add a prefix or remove something.
At first I wrote a python script. It works well, and I want a shell version. Therefore I wrote something like that:
$1 - which directory to list,
$2 - what pattern will be replacement,
$3 - replacement.
echo "usage: dir pattern replacement"
for fname in `ls $1`
do
newName=$(echo $fname | sed "s/^$2/$3/")
echo 'mv' "$1/$fname" "$1/$newName&&"
mv "$1/$fname" "$1/$newName"
done
It works but very slowly, probably because it needs to create a process (here sed and mv) and destroy it and create same process again just to have a different argument. Is that true? If so, how to avoid it, how can I get a faster version?
I thought to offer all processed files a name (using sed to process them at once), but it still needs mv in the loop.
Please tell me, how you guys do it? Thanks. If you find my question hard to understand please be patient, my English is not very good, sorry.
--- update ---
I am sorry for my description. My core question is: "IF we should use some command in loop, will that lower performance?" Because in for i in {1..100000}; do ls 1>/dev/null; done creating and destroying a process will take most of the time. So what I want is "Is there any way to reduce that cost?".
Thanks to kev and S.R.I for giving me a rename solution to rename files.
Every time you call an external binary (ls, sed, mv), bash has to fork itself to exec the command and that takes a big performance hit.
You can do everything you want to do in pure bash 4.X and only need to call mv
pat_rename(){
if [[ ! -d "$1" ]]; then
echo "Error: '$1' is not a valid directory"
return
fi
shopt -s globstar
cd "$1"
for file in **; do
echo "mv $file ${file//$2/$3}"
done
}
Simplest first. What's wrong with rename?
mkdir tstbin
for i in `seq 1 20`
do
touch tstbin/filename$i.txt
done
rename .txt .html tstbin/*.txt
Or are you using an older *nix machine?
To avoid re-executing sed on each file, you could instead setup two name streams, one original, and one transformed, then sip from the ends:
exec 3< <(ls)
exec 4< <(ls | sed 's/from/to/')
IFS=`echo`
while read -u3 orig && read -u4 to; do
mv "${orig}" "${to}";
done;
I think you can store all of file names into a file or string, and use awk and sed do it once instead of one by one.
This question already has answers here:
Rename multiple files based on pattern in Unix
(24 answers)
Closed 5 years ago.
I have loads of files which look like this:
DET01-ABC-5_50-001.dat
...
DET01-ABC-5_50-0025.dat
and I want them to look like this:
DET01-XYZ-5_50-001.dat
...
DET01-XYZ-5_50-0025.dat
How can I do this?
There are a couple of variants of a rename command, in your case, it may be as simple as
rename ABC XYZ *.dat
You may have a version which takes a Perl regex;
rename 's/ABC/XYZ/' *.dat
for file in *.dat ; do mv $file ${file//ABC/XYZ} ; done
No rename or sed needed. Just bash parameter expansion.
Something like this will do it. The for loop may need to be modified depending on which filenames you wish to capture.
for fspec1 in DET01-ABC-5_50-*.dat ; do
fspec2=$(echo ${fspec1} | sed 's/-ABC-/-XYZ-/')
mv ${fspec1} ${fspec2}
done
You should always test these scripts on copies of your data, by the way, and in totally different directories.
You'll need to learn how to use sed http://unixhelp.ed.ac.uk/CGI/man-cgi?sed
And also to use for so you can loop through your file entries http://www.cyberciti.biz/faq/bash-for-loop/
Your command will look something like this, I don't have a term beside me so I can't check
for i in `dir` do mv $i `echo $i | sed '/orig/new/g'`
I like to do this with sed. In you case:
for x in DET01-*.dat; do
echo $x | sed -r 's/DET01-ABC-(.+)\.dat/mv -v "\0" "DET01-XYZ-\1.dat"/'
done | sh -e
It is best to omit the "sh -e" part first to see what will be executed.
All of these answers are simple and good. However, I always like to add an interactive mode to these scripts so that I can find false positives.
if [[ -n $inInteractiveMode ]]
then
echo -e -n "$oldFileName => $newFileName\nDo you want to do this change? [Y/n]: "
read run
[[ -z $run || "$run" == "y" || "$run" == "Y" ]] && mv "$oldFileName" "$newFileName"
fi
Or make interactive mode the default and add a force flag (-f | --force) for automated scripts or if you're feeling daring. And this doesn't slow you down too much: the default response is "yes, I do want to rename" so you can just hit the enter key at each prompt (because of the -z $run test.