I have the following input.
Entities can be 'MG SOCRATES' 'VALIND' 'SUSBTECT'
This entities have the following directories.
/home/tca/git/tca/<entity>
Example :
/home/tca/git/tca/MG SOCRATES
/home/tca/git/tca/VALIND
/home/tca/git/tca/SUSBTECT
In each directory I have files with this pattern something_or-something.
For example:
/home/tca/git/tca/SUSBTECT/asdsad2018-01-01-2018-12-31sdadsda
/home/tca/git/tca/SUSBTECT/asdsad2018-01-01_2018-12-31sdsadadsda
I want to move them to a subdicretory for each entity with the following structure.
I have for example this subdirectory.
/home/tca/git/tca/SUSBTECT/2018-01-01_2018-12-31
What I want to do is to move from /home/tca/git/tca/SUSBTECT/ all files that matches the pattern _or- to the subdirectory.
My code does it correctly but fails for entity 'MG SOCRATES' because there is a space that moves command can not interpretate.
My code:
entity_path="$entity_path""/*"$file_start"*"$file_end"*"
echo `mv -t "$path" $entity_path`
Where $entity_path all files matching the pattern and $path is the directory where I want to move my files.
I think is a problem about the spaces.
Any idea?
There is no way to have a regular parameter expansion that undergoes pathname expansion, but not word-splitting, so your attempt to put the pattern in entity_path is going to fail. Either use the pattern directly,
mv -t "$path" "$entity_path"/*"$file_start"*"$file_end"*
or store the result of the pathname expansion in an array.
entities=( "$entity_path"/*"$file_start"*"$file_end"* )
mv -t "$path" "${entities[#]}"
Related
I'm trying to convert 3,000 or so .svg files from CapitalCase to camelCase.
Current:
-Folder
--FileName1
--FileName2
--FileName3
Goal:
-Folder
--fileName1
--fileName2
--fileName3
How can I use terminal to change the casing on the first character with to lowercase?
Currently I've been trying something along these lines: for f in *.svg; do mv -v "$f" "${f:1}"; done
All files in the folder start with a letter or number.
This can be done very succinctly in zsh with zmv:
autoload zmv
zmv -nvQ '(**/)(?)(*.svg)(.)' '$1${(L)2}$3'
This will recurse through any number of directory levels, and can handle name collisions and other edge cases.
Some of the pieces:
-n: no execution. With this option, zmv will only report what changes it would make. It's a dry run that can be used to test out the patterns. Remove it when you're ready to actually change the names.
-v: verbose.
-Q: qualifiers. Used to indicate that the source pattern includes a glob qualifier (in our case (.)).
'(**/)(?)(*.svg)(.)': source pattern. This is simply a regular zsh glob pattern, divided into groups with parentheses. The underlying pattern is **/?*.svg(.). The pieces:
(**/): directories and subdirectories. This will match any number of directory levels (to only affect the current directory, see below).
(?): matches a single character at the start of the file name. We'll convert this to lowercase later.
(*.svg): matches the rest of the file name.
(.): regular files only. This is a zsh glob qualifier; zmv recognizes it as a qualifier instead of a grouping because of the -Q option. The . qualifier limits the matching to regular files so that we don't try to rename directories.
'$1${(L)2}$3': destination pattern. Each of the groupings in the source pattern is referenced in order with $1, $2, etc.
$1: the directory. This could contain multiple levels.
${(L)2}: The first letter in the file name, converted to lowercase. This uses the L parameter expansion flag to change the case.
The l expansion modifier will also work: $2:l.
The conversion can handle non-ASCII characters, e.g. Éxito would
become éxito.
$3: the rest of the file name, including the extension.
Variations
This will only change files in the current directory:
zmv -nv '(?)(*.svg)' '$1:l$2'
The source pattern in the following version will only match files that start with an uppercase letter. Since the zmv utility won't rename files if the source and destination match, this isn't strictly necessary, but it will be slightly more efficient:
zmv -nvQ '(**/)([[:upper:]])(*.svg)(.)' '$1${(L)2}$3'
More information
zmv documentation:
https://zsh.sourceforge.io/Doc/Release/User-Contributions.html#index-zmv
zsh parameter expansion flags:
https://zsh.sourceforge.io/Doc/Release/Expansion.html#Parameter-Expansion-Flags
Page with some zsh notes, including a bunch of zmv examples:
https://grml.org/zsh/zsh-lovers.html
Solving in bash, tested and working fine, be careful though with your files you working on.
Renaming files in current directory where this script is (1st arg then'd be .) or provide a path, it's do lower-casing of the first letter, if it was uppercase, and yet nothing if it was a number, argument must be provided:
# 1 argument - folder name
# 2 argument - file extension (.txt, .svg, etc.)
for filename in $(ls "$1" | grep "$2")
do
firstChar=${filename:0:1}
restChars=${filename:1}
if [[ "$firstChar" =~ [A-Z] ]] && ! [[ "$firstChar" =~ [a-z] ]]; then
toLowerFirstChar=$(echo $firstChar | awk '{print tolower($0)}')
modifiedFilename="$toLowerFirstChar$restChars"
mv "$1/$filename" "$1/$modifiedFilename"
else
echo "Non-alphabetic or already lowercase"
# here may do what you want fith files started with numbers in name
fi
done
Use: bash script.sh Folder .txt
ATTENTION: Now here after running script and renaming, names of some files may coincide and there would be a conflict in this case. Can later fix it and update this script.
I am trying to copy a .nii file (Gabor3.nii) path to a variable but even though the file is found by the find command, I can't copy the path to the variable.
find . -type f -name "*.nii"
Data= '/$PWD/"*.nii"'
output:
./Gabor3.nii
./hello.sh: line 21: /$PWD/"*.nii": No such file or directory
What went wrong
You show that you're using:
Data= '/$PWD/"*.nii"'
The space means that the Data= parts sets an environment variable $Data to an empty string, and then attempts to run '/$PWD/"*.nii"'. The single quotes mean that what is between them is not expanded, and you don't have a directory /$PWD (that's a directory name of $, P, W, D in the root directory), so the script "*.nii" isn't found in it, hence the error message.
Using arrays
OK; that's what's wrong. What's right?
You have a couple of options. The most reliable is to use an array assignment and shell expansion:
Data=( "$PWD"/*.nii )
The parentheses (note the absence of spaces before the ( — that's crucial) makes it an array assignment. Using shell globbing gives a list of names, preserving spaces etc in the names correctly. Using double quotes around "$PWD" ensures that the expansion is correct even if there are spaces in the current directory name.
You can find out how many files there are in the list with:
echo "${#Data[#]}"
You can iterate over the list of file names with:
for file in "${Data[#]}"
do
echo "File is [$file]"
ls -l "$file"
done
Note that variable references must be in double quotes for names with spaces to work correctly. The "${Data[#]}" notation has parallels with "$#", which also preserves spaces in the arguments to the command. There is a "${Data[*]}" variant which behaves analogously to "$*", and is of similarly limited value.
If you're worried that there might not be any files with the extension, then use shopt -s nullglob to expand the globbing expression into an empty list rather than the unexpanded expression which is the historical default. You can unset the option with shopt -u nullglob if necessary.
Alternatives
Alternatives involve things like using command substitution Data=$(ls "$PWD"/*.nii), but this is vastly inferior to using an array unless neither the path in $PWD nor the file names contain any spaces, tabs, newlines. If there is no white space in the names, it works OK; you can iterate over:
for file in $Data
do
echo "No white space [$file]"
ls -l "$file"
done
but this is altogether less satisfactory if there are (or might be) any white space characters around.
You can use command substitution:
Data=$(find . -type f -name "*.nii" -print -quit)
To prevent multiline output, the -quit option stop searching after the first file was found(unless you're sure only one file will be found or you want to process multiple files).
The syntax to do what you seem to be trying to do with:
Data= '/$PWD/"*.nii"'
would be:
Data="$(ls "$PWD"/*.nii)"
Not saying it's the best approach for whatever you want to do next of course, it's probably not...
I want to write a script that takes a name of a folder as a command line argument and produces a file that contains the names of all subfolders with size 0 (empty subfolder). This is what I got:
#!/bin/bash
echo "Name of a folder'
read FOLDER
for entry in "$search_dir"/*
do
echo "$entry"
done
your script doesn't have the logic you intended. find command has a feature for this
$ find path/to/dir -type d -empty
will print empty directories starting from the given path/to/dir
I would suggest you accept the answer which suggests to use find instead. But just to be complete, here is some feedback on your code.
You read the input directory into FOLDER but then never use this variable.
As an aside, don't use uppercase for your private variables; this is reserved for system variables.
You have unpaired quotes in the prompt string. If the opening quote is double, you need to close with a double quote, or vice versa for single quotes.
You loop over directory entries, but do nothing to isolate just the ones which are directories, let alone empty directories.
Finally, nothing in your script uses Bash-only facilities, so it would be safe and somewhat more portable to use #!/bin/sh
Now, looping over directories can be done by using search_dir/*/ instead of just search_dir/*; and finding out which ones are empty can be done by checking whether a wildcard within the directory returns just the directory itself. (This assumes default globbing behavior -- with nullglob you would make a wildcard with no matches expand to an empty list, but this is problematic in some scenarios so it's not the default.)
#!/bin/bash
# read -p is not POSIX
read -p "Name of a folder" search_dir
for dir in "$search_dir"/*/
do
# [[ is Bash only
if [[ "$dir"/* = "$dir/*" ]]; then # Notice tricky quoting
echo "$dir"
fi
done
Using the wildcard expansion with [ is problematic because it is not prepared to deal with a wildcard expansion -- you get "too many arguments" if the wildcard expands into more than one filename -- so I'm using the somewhat more mild-tempered Bash replacement [[ which copes just fine with this. Alternatively, you could use case, which I would actually prefer here; but I've stuck to if in order to make only minimal changes to your script.
I've modified this script from the arch forums: https://wiki.archlinux.org/index.php/Convert_Flac_to_Mp3#With_FFmpeg
I'm trying to find specific file types in a directory structure, convert them to another music file type, and place them in a "converted" directory that maintains the same directory structure.
I'm stuck at stripping the string $b of its file name.
$b holds the string ./converted/alt-j/2012\ an\ awesome\ wave/01\ Intro.flac
Is there a way I can remove the file name from the string? I don't think ffmpeg can create/force parent directories of output files.
#!/bin/bash
# file convert script
find -type f -name "*.flac" -print0 | while read -d $'\0' a; do
b=${a/.\//.\/converted/}
< /dev/null ffmpeg -i "$a" "${b[#]/%flac/ogg}"
#echo "${b[#]/%flac/ogg}"
I'm stuck at stripping the string $b of its file name.
Let us start with b:
$ b=./converted/alt-j/2012\ an\ awesome\ wave/01\ Intro.flac
To remove the file name, leaving the path:
$ c=${b%/*}
To verify the result:
$ echo "$c"
./converted/alt-j/2012 an awesome wave
To make sure that directory c exists, do:
$ mkdir -p "$c"
Or, all in one step:
$ mkdir -p "${b%/*}"
How it works
We are using the shell's suffix removal feature. In the form ${parameter%word}, the shell finds the shortest match of word against the end of parameter and removes it. (Note that word is a shell glob, not a regex.) In out case, word is /* which matches a slash followed by any characters. Because this removes the shortest such match, this removes only the filename part from the parameter.
Suffix Removal Detailed Documentation
From man bash:
${parameter%word} ${parameter%%word}
Remove matching suffix pattern. The word is expanded to produce a pattern just as in pathname expansion. If the pattern
matches a trailing portion of the expanded value of parameter, then the result of the expansion is the expanded value of
parameter with the shortest matching pattern (the %'' case) or the longest matching pattern (the%%'' case) deleted.
If parameter is # or *, the pattern removal operation is applied to each positional parameter in turn, and the expansion
is the resultant list. If parameter is an array variable subscripted with # or *, the pattern removal operation is
applied to each member of the array in turn, and the expansion is the resultant list.
I have a simple test bash script which looks like that:
#!/bin/bash
cmd="rsync -rv --exclude '*~' ./dir ./new"
$cmd # execute command
When I run the script it will copy also the files ending with a ~ even though I meant to exclude them. When I run the very same rsync command directly from the command line, it works! Does someone know why and how to make bash script work?
Btw, I know that I can also work with --exclude-from but I want to know how this works anyway.
Try eval:
#!/bin/bash
cmd="rsync -rv --exclude '*~' ./dir ./new"
eval $cmd # execute command
The problem isn't that you're running it in a script, it's that you put the command in a variable and then run the expanded variable. And since variable expansion happens after quote removal has already been done, the single quotes around your exclude pattern never get removed... and so rsync winds up excluding files with names starting with ' and ending with ~'. To fix this, just remove the quotes around the pattern (the whole thing is already in double-quotes, so they aren't needed):
#!/bin/bash
cmd="rsync -rv --exclude *~ ./dir ./new"
$cmd # execute command
...speaking of which, why are you putting the command in a variable before running it? In general, this is a good way make code more confusing than it needs to be, and trigger parsing oddities (some even weirder than this). So how about:
#!/bin/bash
rsync -rv --exclude '*~' ./dir ./new
You can use a simple --eclude '~' as (accoding to the man page):
if the pattern starts with a / then it is anchored to a particular spot in
the hierarchy of files, otherwise it
is matched against the end of the
pathname. This is similar to a leading
^ in regular expressions. Thus "/foo"
would match a name of "foo" at either
the "root of the transfer" (for a
global rule) or in the merge-file's
directory (for a per-directory rule).
An unqualified "foo" would match a
name of "foo" anywhere in the tree
because the algorithm is applied
recursively from the top down; it
behaves as if each path component gets
a turn at being the end of the
filename. Even the unanchored
"sub/foo" would match at any point in
the hierarchy where a "foo" was found
within a directory named "sub". See
the section on ANCHORING
INCLUDE/EXCLUDE PATTERNS for a full
discussion of how to specify a pattern
that matches at the root of the
transfer.
if the pattern ends with a / then it will only match a directory, not a
regular file, symlink, or device.
rsync chooses between doing a simple string match and wildcard
matching by checking if the pattern
contains one of these three wildcard
characters: '*', '?', and '[' .
a '*' matches any path component, but it stops at slashes.
use '**' to match anything, including slashes.