I have the script below which I run with from within a directory of images with ./checkres.sh * or even *.jpg, but it only works if there is a small number of files, less than 100 or so. Anything more and it stops with error "Argument list too long" and I have 100,000's of images to process.
I been looking all over the web for hours and tried dozens of different changes, using a while instead of a for loop and various other changes to variables but nothing seems to work.
Can anyone help solve this mystery as I'm new to bash scripting and have run out of ideas.
#!/bin/bash
for img in "$#"
do
export height=`identify -format %h "$img"`
export width=`identify -format %w "$img"`
let ratio=$width/$height
echo Image "$img" = [ $width x $height ] = $ratio
if [ $ratio == 0 ]
then
echo Portrait 0
convert "$img" -geometry 600x800 -format jpeg -quality 80 "$img"
else
echo Landscape 1
convert "$img" -geometry 800x600 -format jpeg -quality 80 "$img"
fi
You don't need to change anything about your script; just change how you invoke it:
find . -maxdepth 1 -name '*.jpg' -exec ./checkres.sh '{}' +
This will put only as many filenames on each invocation as will fit, running the script more than once as necessary.
In fact -- unless you change the calling convention, you can't fix this bug by changing anything about your script, since the problem is happening while the shell is trying to start it!
However, there's another workaround -- instead of defining this as an external script, you could define it as a shell function (in your ~/.bashrc or similar), which means it doesn't need to be started as an external command, meaning the operating system limits in question don't apply.
checkres() {
for img; do
...
done
}
checkres *.jpg
...is executed entirely within the shell, mooting any limits on the length of the command line which can be passed to an external command during its execution.
If you want to take a directory argument:
for img in "$1"/*; do
Quoting correctly this way will ensure that your code can deal with directory names containing spaces.
Related
I have a folder with regular pictures, and another with resized ones.
The goal is to check if a picture is not resized, do the resizing and save in another folder.
I'm using an echo for simplicity, because I don't have the comparison working.
for file in ../regular/*.jpg;
do
img=`basename "$file"`
FILE=./resized/$img
if [ ! -f "$FILE" ]; then
echo "$img NOT RESIZED"
fi
done
This code just echoes NOT RESIZED for all the pictures in the regular folder i.e. it doesn't seem to make the comparison at all.
Where is my mistake?
for file in ../regular/*.jpg;
FILE=./resized/$img
Try to use absolute path, You can also add echo $FILE to see what scripts tries to verify
If this directory contains a huge amount of files, you can exceed command line length limit (usually ~4kb-32kb)
You are using quotas in basename command, why? If your images could contain spaces, you should use quotas also in "if" command, check script below
for file in ../regular/*.jpg;
do
img=$(basename "$file")
if [ ! -f "./resized/$img" ]; then
echo "$img NOT RESIZED"
fi
done
You should try to use diff command to compare directories:
diff -r "$PATH1" "$PATH2"
I have multiple files in a directory that are in .aiff format, and I would like to convert them to .wav using SoX. I have tried the code on this website, which is as follows
theFiles = `/Users/me/RainbowAiff/*.aiff`;
for eachFile in $theFiles; do v1=${eachFile%*.aiff};
oldFile="$v1.aiff"; newFile = "$v1.wav";
echo oldFile $oldFile; echo newFile $newFile; sox $oldFile $newFile; done
and this website, which is as follows
for i in `/Users/me/RainbowAiff/ *.aiff`; do echo -e "$i"; sox $i $i.wav; echo -e "$i.wav"; done;
but I get an error message in both instances that says "cannot execute binary file". What could be the source of this error?
Incompatible binary files cause this error, see this question.
If the outputs from file sox and uname -a commands tell that there is a discrepancy between the binary file and your operating system, i.e., x86 vs. ARM, you need to find the correct binary for SoX. Although there is no SoX release since 2015, check this and this to find a version that is compatible with your system.
The problem is also related to the file attributes of the sox. In order to give it the executable attribute, simply go to the folder where sox is located and run chmod +x sox command (you need root access first).
The two snippets you linked to looks a little sketchy to me with their use of ls and echo.
echo -e might in fact be what gave you the syntax error, as OSX (BSD) echo does not have an -e option. (type man echo into the terminal to see).
I took the liberty of assembling an alternative one-liner, based around find and its -exec option.
find -E . -maxdepth 1 -iregex '.*\.(aif|aiff)' -exec bash -c '$0 "$1" "${1%.*}.wav"' sox {} \;
To explain it:
find: can find about anything
-E: enables extended regex*
.: starts in current working directory. Can be replaced with relative or absolute path
-maxdepth 1: looks only in the first directory layer. Replace 1 with 2 to also find files within folders, with 3 to also find files within folders within folders …
Removing this option will enable full recursion (find files all the way down)
-iregex: use case-insensitive regular expressions
'.*\.(aif|aiff)': match anything that starts with anything (.) at any length (*), followed by a period (\.) and the strings 'aif' or 'aiff' (or 'AIF', 'AIFF', 'aiFF' …) ((aif|aiff))
-exec bash -c : execute the following in a non-interactive bash shell
Right around here it gets a bit more complicated
'$0 "$1" "${1%.*}.wav"': this is the call to be run inside the bash shell. $0 will expand to the program name. "$1" will expand to the first (and in this case, only) argument, enclosed in double quotes in case of white-spaces. "${1%.*}.wav" will also expand to the first argument, except it will strip away the last period and everything after, before tagging on '.wav' at the end. Effectively replacing the file extension.
sox {}: these are the arguments passed to the shell call. The first (sox) is the program we want to use, referred to as $0 within the call. The second ({}) is whatever file find has found, and referred to as $1 within the shell call
That's quite a mouthful and I'm no expert, so there might be some mistakes in what I've written, though the general outline should be solid.
*This is OSX specific, a less pretty but more portable option would be:
find . -maxdepth 1 \( -iname "*.aif" -o -iname "*.aiff" \) -exec bash -c '$0 "$1" "${1%.*}.wav"' sox {} \;
I want to run a program (anchor) in all the .fa files in a directory, and append the output back into the input files (as next lines of original input content). For that I have tried:
for f in ./*.fa ; do ./anchor $f -d ./; done >> $f
and it gives the error:
bash: $f: ambiguous redirect
I understand bash is objecting to post the output content in the input file, but as I am recently migrating from windows system, there I'm doing it as:
for %F in ("*.fa") do anchor %F -d ./ >> %%F
which gives me the desired output.
Although this might seems strange to append output in input files, but how can I do that in shell?
Thanks
ps. I also try to use $$ in output redirection, but it forms a separate output file with different name and the original input content are also not merged in it.
the most logical way of doing it is to redirect from inside the loop instead, but not directly (thanks 123 for the comment: file cannot be input and output as the same time, maybe here it could work since windows loop seems to work, but let's not take useless risks...)
for f in ./*.fa ; do ./anchor $f -d ./ > /tmp/something; cat /tmp/something >> $f; done
BTW: I wouldn't dare trying to explain what your original code does, is f defined/when $f is evaluated (before or after entering the for loop).
My guess is that $f just not evaluated and considered as $f literally, which confuses bash.
At any rate, it's incorrect.
EDIT: the windows version
for %F in ("*.fa") do anchor %F -d ./ >> %%F
does the redirection inside the loop (unlike your unix attempt), and it's really surprising that it works because of windows file locking...
What could happen (not sure) is that windows doesn't try to append to the file before something is issued on standard output, and at that moment, the program has closed the file as input.
I am new to Ubuntu and learning bash script by googling around. I want to know how to load image files from a folder and save it in an array in bash script.
Probably am not doing a really smart search, but if anyone knows how to do it already, can you please help?
I am planning to get the path from the command line argument, so $1 will have the path, as far as I have read.
Thus, I have this code
#!/bin/bash
for f in "$1"
do
echo "$f"
done
But the output just prints 1 file instead of all 36 files. Can you please help me here?
Note : the input am giving is of this format
/path/*.png
That glob (/path/*.png) has already been expanded by the shell when your script is called.
You have all the filenames in $# (the array of all the positional parameters to the script/function).
Try
echo "$#"
to see them or
for file in "$#"; do
echo "$file"
done
The default list for in is $# so you can use for file; do in place of for file in "$#"; do if you want.
Below is my attempt at this problem. It's a functional script, but I have to specify the application to be used for each file type. Since this information regarding default application must be stored somewhere on Linux / Ubuntu already, how may I access them and incorporate into my script?
Also, can my script be more "elegant" in any way?
Thank you for helping a Bash script beginner! I appreciate any comment.
#!/bin/bash
# Open the latest file in ~/Downloads
filename=$(ls -t ~/Downloads | head -1)
filetype=$(echo -n $filename | tail -c -3)
if [ $filetype == "txt" ]; then
leafpad ~/Downloads/$filename
elif [ $filetype == "pdf" ]; then
evince ~/Downloads/$filename
fi
How do I open a file in its default program - Linux should help you with the first part of your question:
xdg-open ~/Downloads/$filename
As mentioned in other answers, it's best not to trust the output of ls in scripts, especially if you have unusual characters like newlines in your filenames. One way to robustly get a list of filenames in a script is with the find command, and null-delimiting them into a pipe.
So to answer your question with a one-liner:
find ~/Downloads -maxdepth 1 -type f -printf "%C# %p\0" | sort -zrn | { read -d '' ts file; xdg-open "$file"; }
Breaking it down:
The find command lists files in the ~/Download directory, but doesn't descend any deeper into subdirectories. The filenames are printed with the given printf format, which lists a numerical timestamp, followed by a space, followed by a null delimiter. Note the printf format specifiers for find are different to those for regular printf
The sort command numerically sorts (-n) the resulting null-delimited list (-z) by the first field (numerical timestamp). Sort order is reversed (-r) so that the latest entry is displayed first
The read command reads the timestamp and filename of the first file in the list into the ts and file variables. -d '' tells read to use null delimiters.
The file is opened using xdg-open.
Note the read and xdg-open commands are in a curly bracket inline group, so the file variable is in scope for both.
Welcome to bash programming. :-)
First off, I'll refer you to the Bash FAQ. Great resource, lots of tips, perspectives and warnings.
One of them is the classic Parsing LS problem that your script suffers from. The basic idea is that you don't want to trust the output of the ls command, because special characters like spaces and control characters may be represented in a way that doesn't allow you to refer to the file.
You're opening the "last" file, as determined by a sort that the ls command is doing. In order to detect the most recent file without ls, we'll need some extra code. For example:
#!/bin/sh
last=0
for filename in ~/Downloads/*; do
when=$(stat -c '%Y' "$filename")
if [ $when -gt $last ]; then
last=$when
to_open="$filename"
fi
done
xdg-open "$to_open"
The idea is that we'll walk through each file in your Downloads directory and fine the one with the largest timestamp using the stat command. Then open that file using xdg-open, which may already be installed on your system because it's part of a tool set that's a dependency for a number of other applications.
If you don't have xdg-open, you can probably install it from the xdg-utils package which using whatever package management system is around for your Linux distro.
Another possibility is gnome-open, which is part of the Gnome desktop (the libgnome package, to be precise). YMMV. We'd need to know more about your distro and your desktop environment to come up with better advice.
Note that if you do want to continue selecting your application by extension, you might want to consider using a switch instead of a series of ifs:
...
case "${filename##*.}" in
txt)
leafpad "$filename"
;;
pdf)
xdg-open "$filename"
;;
*)
echo "ERROR: can't open '$filename'" >&2
;;
esac
mimeopen might be useful? There's an explanation of Mime types here.
Also - are your filetype extensions always exactly two letters, as the tail -c -3 implies? If they're of variable length, you may want a regular expression instead.
As previously mentioned, xdg-open and mimeopen may be useful and more elegant; from their manpages:
xdg-open opens a file or URL in the user's preferred application. If a URL is provided the URL will be opened in the user's preferred web browser. If a file is provided the file will be opened in the preferred application for files of that type.
[mimeopen] tries to determine the mimetype of a file and open it with the default desktop application. If no default application is configured the user is prompted with an "open with" menu in the terminal.
For more elegance in the original script, replace
filetype=$(echo -n $filename | tail -c -3)
with
filetype=${filename: -3}
and instead of the five-lines if/elif/fi structure, consider using two lines as follows.
[ $filetype == "txt" ] && leafpad ~/Downloads/$filename
[ $filetype == "pdf" ] && evince ~/Downloads/$filename