This question already has answers here:
How do I rename the extension for a bunch of files?
(28 answers)
Closed 6 years ago.
I'm having trouble manipulating strings in bash. I wish to re-write extensions.
I have the following 2 files in a Downloads directory
Example 001.mkv
Example 002.mkv
Using the script below I always get the same filenames returned without .mkv rewritten into .mp4.
find /Downloads -name \*.mkv -execdir echo $(file={}; echo ${file/mkv/mp4};) \;
I understand this isn't all you need to re-format a file but this script is part of a larger script that is passed to FFMPEG.
Here is the full command with FFMPEG.
find /Downloads -name \*.mkv -execdir ffmpeg -i {} -vcodec copy -acodec copy $(file={}; echo ${file/mkv/mp4};) \;
The exec and execdir are generally intended to actually execute command not to echo/print info about the files found (print/printf).
There are several ways to do this and here's one.
You could first try using the rename command that can use regex substitution for renaming files. This would require all the files to be renamed to be in the same folder /Downloads: (syntax may very according to the implementation of rename that ships with your distro)
ls *.mkv
a.mkv b.mkv
rename .mkv .mp4 *.mkv
ls *.mp4
a.mp4
b.mp4
Let's suppose that the mkv files are also present in subdirectories of /Downloads:
find . -type f -name "*.mkv"
./sundir/d.mkv
./sundir/c.mkv
./a.mkv
./b.mkv
find -type f -name "*.mkv" -exec rename .mkv .mp4 {} \;
find . -type f -name "*.mp4"
./sundir/c.mp4
./sundir/d.mp4
./b.mp4
./a.mp4
You can try using bash -c to execute your command
find /Downloads -name \*.mkv -execdir bash -c 'file={}; echo ${file/.mkv/.mp4}' \;
Related
This question already has answers here:
Rename multiple files based on pattern in Unix
(24 answers)
Closed 3 years ago.
I have several files with the extension *.php in different subfolders in the folder /root/Hello. I try to rename all .php files to .html but I want to keep the structure i.e. the path to the file should remain identical.
I found all files with the following command:
find /root/Hello -name "*.php"
But I don't know how I can rename all files with *.php to *.html and keep the structure I think I must use:
-exec
But I don't which argument I should use with -exec
use find:
find /path -depth -name "*.php" -exec sh -c 'mv "$1" "${1%.php}.html"' _ {} \;
I'm writing a shell script on a Linux machine to be run via a crontab which is meant to move all files older than the current day to a new folder, and then tar and zip the entire folder. Seems like a simple task but for some reason, I'm running into all kinds of roadblocks. I'm new to this and self-taught so any help or redirection would be greatly appreciated.
Specific criteria for which files to archive:
All log files are in /home/tech/logs/ and all pdfs are in /home/tech/logs/pdf
All files are over a day old as indicated by the file name (file name does not include $CURRENT_DATE)
All files must be *.log or *.pdf (i.e. don't archive files that don't include $CURRENT_DATE if it isn't a log or pdf file.
Filename formatting specifics:
All the log file names are in home/tech/logs in the format NAME 00_20180510.log, and all the pdf files are in a "pdf" subdirectory (home/tech/logs/pdf) with the format NAME 00_20180510_00000000.pdf ("20180510" would be whenever the file was created and the 0's would be any number). I need to use the name rather than the file metadata for the creation date, and all files (pdf/log) whose name does not include the current date are "old". I also can't just move all files that don't contain $CURRENT_DATE in the name because it would take any non-*.pdf or *.log files with it.
Right now the script creates a new folder with a new pdf subdir for the old files (mkdir -p /home/tech/logs/$ARCHIVE_NAME/pdf). I then want to move the old logs into $ARCHIVE_NAME, and move all old pdfs from the original pdf subdirectory into $ARCHIVE_NAME/pdf.
Current code:
find /home/tech/logs -maxdepth 1 -name ( "*[^$CURRENT_DATE].log" "*.log" ) -exec mv -t "$ARCHIVE_NAME" '{}' ';'
find /home/tech/logs/pdf -maxdepth 1 -name ( "*[^$CURRENT_DATE]*.pdf" "*.pdf" ) -exec mv -t "$ARCHIVE_NAME/pdf" '{}' ';'
This hasn't been working because it treats the numbers in $CURRENT_DATE as a list of numbers to exclude rather than a literal string.
I've considered just using tar's exclude options like this:
tar -cvzPf "$ARCHIVE_NAME.tgz" --directory /home/tech/logs --exclude="$CURRENT_DATE" --no-unquote --recursion --remove-files --files-from="/home/tech/logs/"
But a) it doesn't work, and b) it would theoretically include all files that weren't *.pdf or *.log files, which would be a problem.
Am I overcomplicating this? Is there a better way to go about this?
I would go about this using bash's extended glob features, which allow you to negate a pattern:
#!/bin/bash
shopt -s extglob
mv /home/tech/logs/*!("$CURRENT_DATE")*.log "$ARCHIVE_NAME"
mv /home/tech/logs/pdf/*!("$CURRENT_DATE")*.pdf "$ARCHIVE_NAME"/pdf
With extglob enabled, !(pattern) expands to everything that doesn't match the pattern (or list of pipe-separated patterns).
Using find it should also be possible:
find /home/tech/logs -name '*.log' -not -name "*$CURRENT_DATE*" -exec mv -t "$ARCHIVE_NAME" {} +
Building on #tom-fenech answer, optimized to avoid many mv invocations:
find /home/tech/logs -maxdepth 1 -name '*.log' -not -name "*_${CURRENT_DATE?}.log" | \
xargs mv -t "${ARCHIVE_NAME?}"
An interesting feature, from processing the file thru pipes, is the ability to filter them with extra tools (aka grep :), which can (arguably) become more readable i.e. ->
find /home/tech/logs -maxdepth 1 -name '*.log' | fgrep -v "_${CURRENT_DATE?}" | \
xargs mv -t "${ARCHIVE_NAME?}"
Then similarly for the pdf ones, BTW you can "dry-run" above by just replacing mv by echo mv.
--jjo
I have a bash script I cannot get working. I am a dead set beginner in bash this is actually the first script I've ever used. I'm trying to get omxplayer to play a list of files in a directory. When the script runs I get feedback showing the file then the error that there is no such file or directory. Please help me?
#!/bin/sh
find /media/pi/88DC-E668/MP3/ -name "*.mp3" -exec PLAY={} \;; omxplayer "$PLAY";
This is the echo:
find: `PLAY=/media/pi/88DC-E668/MP3/Dance.mp3': No such file or directory
find: `PLAY=/media/pi/88DC-E668/MP3/Whitemary.mp3': No such file or directory
find: `PLAY=/media/pi/88DC-E668/MP3/Limo.mp3': No such file or directory
find: `PLAY=/media/pi/88DC-E668/MP3/Silo.mp3': No such file or directory
File "" not found.
Easy way:
find /media/pi/88DC-E668/MP3 -name \*.mp3 -exec omxplayer {} \;
or
while IFS= read -r -d '' mp3
do
omxplayer "$mp3"
done < <(find /media/pi/88DC-E668/MP3 -name \*.mp3 -print0)
or
find /media/pi/88DC-E668/MP3 -name \*.mp3 -print0 | xargs -0 -n1 omxplayer
You can omit the -n1 if the omxplayer could handle multiple filenames. In such case the 1st could be written as:
find /media/pi/88DC-E668/MP3 -name \*.mp3 -exec omxplayer {} +
but the simplest probably will be
#shopt -s globstar #the default is on
for mp3 in /media/pi/88DC-E668/MP3/{,**/}*.mp3
do
omxplayer "$mp3"
done
EDIT I stand corrected, but won't delete the answer as you can also learn from the mistakes of others. See comment and rather use this answer :)
So please don't do it like this, as this is a typical "happy path" solution - meaning: it works if you know what you're doing and you know your paths (e.g. that they don't contain spaces). I keep forgetting that many people don't know yet that spaces in paths are evil.
Just use xargs to pass what you found to your player like this:
#!/bin/sh
find /media/pi/88DC-E668/MP3/ -name "*.mp3" | xargs omxplayer
The -exec foo part means run the command foo for each path found.
In your case, -exec PATH={}, the {} part is replaced with the path name, ending up with something like -exec PATH=/media/pi/88DC-E668/MP3/Dance.mp3, and so then find tries to run the command PATH=/media/pi/88DC-E668/MP3/Dance.mp3 which fails because there isn't actually any such program to execute.
xargs is the usual way to do what you're trying to do, as described in another comment already.
You could also do:
find /media/pi/88DC-E668/MP3/ -name \*.mp3 |
while read f; do
omxplayer "$f"
done
I have a directory that contains a list of files having the following format:
240-timestamp1.ts
240-timestamp2.ts
...
360-timestamp1.ts
360-timestamp2.ts
Now, I want to implement a bash command which matches the files that start with '240' and renames them so that instead of '240-timestampX.ts' the files look like '240-human-readable-timestampX.ts'.
I have tried the following:
find . -maxdepth 1 -mmin +5 -type f -name "240*"
-exec mv $0 {$0/240-***and here I want to insert
either stat -c %y filename or date -d #timestampX***} '{}' \;
I stuck here because I don't know if I can embed a bash command inside the mv command. I know the task may look a bit confusing and over-complicated, but I would like to know if it is possible to do so. Of course I can create a bash script that would go through all the files in the directory and while loop them with changing their respective names, but somehow I think that a single command would be more efficient (even if less readable).
The OS is Linux Ubuntu 12.04.5
The shell is bash
Thank you both Kenavoz and Kurt Stutsman for the proposed solutions. Both your answers perform the task; however, I marked Kenavoz's answer as the accepted one because of the degree of similarity between my question and Kenavoz's answer. Even if it is indeed possible to do it in a cleaner way with omitting the find command, it is necessary in my case to use the respective command because I need to find files older than X units of time. So thank you both once again!
In case you want to keep your mmin option, your can use find and process found files with a bash command using xargs :
find . -maxdepth 1 -mmin +5 -type f -name "240*.ts" | xargs -L 1 bash -c 'mv "${1}" "240-$(stat -c %y ${1}).ts"' \;
In bash if all your files are in a single directory, you don't need to use find at all. You can do a for loop:
for file in 240-*; do
hr_timestamp=$(date -d $(echo "$file" | sed 's/.*-\([0-9]*\)\.ts/\1/'))
mv "$file" "240-$hr_timestamp.ts"
done
Does a program exist that will allow me to batch covert files and do the following:
a) Search for MP3's on my drive
b) transcode from 128 kbs/sec to 64 kbs/sec
c) switch from Stereo to Mono
d) save and overwrite the previous file
Or is their anyway I could write a script to perform this task on a windows desktop?
if you are on un*x, the tool of choice for batch-processing is find:
find /path/to/foo -name "*.mp3"
will give you all files matching "*.mp3" in /path/to/foo and all it's subdirectories.
sincefind is a beast, you probably want to check it's manpage.
you can pass a script to find that is called for each match. e.g. the following will do an "ls -l" on all files (excluding directories named e.g. "mysounds.mp3/"; that's what the -type f is for):
find /path/to/foo -type f -name "*.mp3" -exec ls -l \{\} \;
note, that the curly braces and the semicolon are escaped with backticks, in order to prevent the shell from interpreting those special characters.
if you have a script (named convertmp3.sh and which is sitting in your current working directory) that does an in-place conversion (overwriting the old file), you can do:
find /path/to/foo -type f -name "*.mp3" -exec ./convertmp3.sh \{\} \;
such a script could look like:
#!/bin/sh
INFILE="$1"
TMPFILE="${INFILE}.mp3"
ffmpeg -i "${INFILE}" -ac 1 -b 64k "${TMPFILE}" && mv "${TMPFILE}" "${INFILE}"
note that i'm using a temporary file here, because else ffmpeg would start overwriting the source file before it has fully read it, and thus stopping with an error.
also do not forget the quotes around the filenames, to protect against filenames with spaces and the like.
also note, that this script does not check whether the input file is already in the desired format, thus potentially re-encoding it.