How do I write a one-liner cd command for the following case? - linux

I have 2 directories
testing_dir
testing_dir_win
So I need to cd to testing_dir. But here is the case
the directories can be
testing_dir or testing_dir-2.1.0
testing_dir_win or testing_dir_win-1.3.0
and my script should only take testing_dir or testing_dir-2.1.0 (based on which is available)
I have the long way of writing it:
str=`ls folder_name|grep ^testing_dir`
arr=(${str//" "/ })
ret=""
for i in "${arr[#]}"
do
if [[ $i != *"testing_dir_win"* ]] ; then
ret=$i
fi
done
but is there a one-liner for this problem? something like cd testing_dir[\-]?(This doesn't work by the way).

If your script contains
shopt -s extglob
you can use:
cd testing_dir?(-[[:digit:]]*) || exit
...if you have a guarantee that only one match will exist.
Without that guarantee, you can directly set
arr=( testing_dir?(-[[:digit:]]*) )
cd "${arr[0]}" || exit

use command with grep filters:
cd `ls | grep -w testing_dir`
this command will match the testing_dir directory without worrying for version.
P.S in case of many versions it will go inside the earliest version so add "head -1, tail -1" according to your usecase

Related

Bash regex for just numbers and dots

There's a folder with two files in it like: filename-3.0.1-extra.jar and filename-3.0.1.jar. The number and dots in the middle are the version, which can change. I'm trying to copy filename-3.0.1.jar to another folder.
Something like:
cp folder1/filename-*.jar otherfolder/
But the wildcard * matches both files. I'm trying to copy just the file without the -extra at the end. So I'm trying to match filename on just numbers and dots when I copy, something like this:
cp folder1/filename-[0-9.].jar otherfolder/.
But that's not the right syntax for the regex. Would appreciate any help here!
UPDATE:
I got it somewhat working with this:
ls | grep -e "filename-[0-9]\.[0-9]\.[0-9]\.jar"
But the regex seems a bit rigid. Is there a way to shorten it to something like "filename-([0-9]+[\.])+jar"?
So that even cases like filename-32.430.3.jar would also get captured?
Using extglob you can do this:
shopt -s extglob
cp folder1/filename-+([0-9.]).jar otherfolder/
Here +([0-9.]) will match 1 or more of any digits or dots.
Based on your edited question it appears you're trying to use a grep with a regular expression. You can use this grep solution:
printf '%s\n' *.* | grep -E '^filename-([0-9]+\.)+jar$'
filename-3.0.1.jar
you can do something like
cp "folder1/${##*.}" otherfolder
or
cd folder1 && cp -r -v $(echo -e $(ls | grep -e "[0-9]*\.*")) otherfolder/. && cd ..
Given:
$ ls -1 *.jar
filename-3.0.1-extra.jar
filename-3.0.1.jar
You can use a loop and filter out those that match *-extra*:
for fn in *.jar; do # with this glob, what DO you want
[[ $fn != #(*-extra*) ]] && echo "$fn" # and what you DONT want
done
Prints:
filename-3.0.1.jar
So your loop could be:
for fn in *.jar; do
[[ $fn != #(*-extra*) ]] && cp "$fn" otherfolder/
done

How to follow latest log file with tail -f?

I have a dir that saves a fresh log file for each run, so I use the following command to follow the latest log file:
tail -f $(ls -t | head -1)
So my question would be if there is any way to jump from one file to the next, if there is a newer log file available without having to stop the latest tail -f and rerunning it?
What you could attempt is something like this:
# setup a link that points to the youngest file
$ for f in *; do [[ ( ! -f last ) || ( "$f" -nt last) ]] && ln -sf "$f" last; done
# monitor that file continuously by filename
$ tail -F last
run the following in another shell
while :; for f in *; do [ "$f" -nt last ] && ln -sf "$f" last; done
You can also run this in a single command as:
while :; do for f in *; do [[ ( ! -f last ) || ($f -nt last) ]] && ln -sf $f last; done; done & tail -F last
The -F flag for tail is supported in the GNU version. (It is in the GNU CoreUtils collection.)
Apparently, IBM have provide download for the GNU Toolkit for AIX, and apparently it includes a version of tail that supports the -F option.
You could use that in conjunction with the other answers to this question. (For example, using tail -F with a symlink that you refresh regularly.)
Alternatively, if none of those solutions work for you, you could get hold of the GNU CoreUtils source code1, and modify the source for tail to do what you want, and build it yourself on AIX.
1 - I haven't checked, but I expect that the IBM devs will be contributing back any changes they make to CoreUtils to get it to work on AIX. If not, they are obligated to make the source code available to you on request. Either way, getting hold of AIX compatible source code should not be a problem.

Linux: Update directory structure for millions of images which are already in prefix-based folders

This is basically a follow-up to Linux: Move 1 million files into prefix-based created Folders
The original question:
I want to write a shell command to rename all of those images into the
following format:
original: filename.jpg new: /f/i/l/filename.jpg
Now, I want to take all of those files and add an additional level to the directory structure, e.g:
original: /f/i/l/filename.jpg new: /f/i/l/e/filename.jpg
Is this possible to do with command line or bash?
One way to do it is to simply loop over all the directories you already have, and in each bottom-level subdirectory create the new subdirectory and move the files:
for d in ?/?/?/; do (
cd "$d" &&
printf '%.4s\0' * | uniq -z |
xargs -0 bash -c 'for prefix do
s=${prefix:3:1}
mkdir -p "$s" && mv "$prefix"* "$s"
done' _
) done
That probably needs a bit of explanation.
The glob ?/?/?/ matches all directory paths made up of three single-character subdirectories. Because it ends with a /, everything it matches is a directory so there is no need to test.
( cd "$d" && ...; )
executes ... after cd'ing to the appropriate subdirectory. Putting that block inside ( ) causes it to be executed in a subshell, which means the scope of the cd will be restricted to the parenthesized block. That's easier and safer than putting cd .. at the end.
We then collecting the subdirectories first, by finding the unique initial strings of the files:
printf '%.4s\0' * | uniq -z | xargs -0 ...
That extracts the first four letters of each filename, nul-terminating each one, then passes this list to uniq to eliminate duplicates, providing the -z option because the input is nul-terminated, and then passes the list of unique prefixes to xargs, again using -0 to indicate that the list is nul-terminated. xargs executes a command with a list of arguments, issuing the command several times only if necessary to avoid exceeding the command-line limit. (We probably could have avoided the use of xargs but it doesn't cost that much and it's a lot safer.)
The command called with xargs is bash itself; we use the -c option to pass it a command to be executed. That command iterates over its arguments by using the for arg in syntax. Each argument is a unique prefix; we extract the fourth character from the prefix to construct the new subdirectory and then mv all files whose names start with the prefix into the newly created directory.
The _ at the end of the xargs invocation will be passed to bash (as with all the rest of the arguments); bash -c uses the first argument following the command as the $0 argument to the script, which is not part of the command line arguments iterated over by the for arg in syntax. So putting the _ there means that the argument list constructed by xargs will be precisely $1, $2, ... in the execution of the bash command.
Okay, so I've created a very crude solution:
#!/bin/bash
for file1 in *; do
if [[ -d "$file1" ]]; then
cd "$file1"
for file2 in *; do
if [[ -d "$file2" ]]; then
cd "$file2"
for file3 in *; do
if [[ -d "$file3" ]]; then
cd "$file3"
for file4 in *; do
if [[ -f "$file4" ]]; then
echo "mkdir -p ${file4:3:1}/; mv $file4 ${file4:3:1}/;"
mkdir -p ${file4:3:1}/; mv $file4 ${file4:3:1}/;
fi
done
cd ..
fi
done
cd ..
fi
done
cd ..
fi
done
I should warn that this is untested, as my actual structure varies slightly, but I wanted to keep the question/answer consistent with the original question for clarity.
That being said, I'm sure a much more elegant solution exists than this one.

How to convert ".." in path names to absolute name in a bash script?

How to convert the .. in the path names to absolute path names in a bash script. That is, if I have a path /home/nohsib/dvc/../bop, I want this to be changed to the path without dots in it, in this case /home/nohsib/bop
How can I do that?
What you're looking for is readlink:
absolute_path=$(readlink -m /home/nohsib/dvc/../bop)
Please note: You need to use GNU's readlink implementation which offers the "-m" option. BSD's readlink for example does not.
Try:
ABSOLUTE_PATH=$(cd /home/nohsib/dvc/../bop; pwd)
One issue with using :
ABSOLUTE_PATH=$(cd ${possibleDirectory}; pwd)
is that if ${possibleDirectory} doesn't exist, ABSOLUTE_PATH will then be set to the current directory. Which is probably NOT what you want or expect.
I think using this version may be more desirable in general:
ABSOLUTE_PATH=$(cd ${possibleDirectory} && pwd)
If ${possibleDirectory} does not exist or is not accessible, due to missing directory access permissions, ABSOLUTE_PATH will contain the empty string.
The advantage of this is that you can then test for the empty string or let it fail naturally, depending on the circumstances. Defaulting to the current directory in the case of a failed 'cd' command may lead to very unexpected and possibly disastrous results (e.g. rm -rf "$ABSOLUTE_PATH" )
If you want to do it without following any symlinks, then try using realpath with option -s:
$ realpath -s /home/nohsib/dvc/../bop
/home/nohsib/bop
Note that with realpath, normally all but the last component must exist. So for the above to work, the following must all be present in the file system:
/home
/home/nohsib
/home/nohsib/dvc
But you can bypass that requirement using the -m option.
$ realpath -sm /home/nohsib/dvc/../bop
/home/nohsib/bop
(Note realpath is not available on all systems, especially older non-Debian systems. For those working on embedded Linux, unfortunately Busybox realpath doesn't support the -s or -m switches.)
Use
echo Absolute path: $(cd $1; pwd)
Just an additional note, if your current path is under a symlink, you can resolve the true path with this:
pwd -P
To solve your specific problem, this will issue a cd command to change directory to the path without the '..' in it. Note you will be in the same folder, just with the correct path:
cd `pwd -P`
As an alternative to GNU's readlink and realpath, I had created functions as well that would run in scripts independent of external commands like pwd and stuffs.
http://www.linuxquestions.org/questions/blog/konsolebox-210384/getting-absolute-paths-of-unix-directories-and-filenames-in-shell-scripts-3956/
One of those is this one. It will save the absolute path to $__. I used read there to be safe from pathname expansion.
function getabspath {
local -a T1 T2
local -i I=0
local IFS=/ A
case "$1" in
/*)
read -r -a T1 <<< "$1"
;;
*)
read -r -a T1 <<< "/$PWD/$1"
;;
esac
T2=()
for A in "${T1[#]}"; do
case "$A" in
..)
[[ I -ne 0 ]] && unset T2\[--I\]
continue
;;
.|'')
continue
;;
esac
T2[I++]=$A
done
case "$1" in
*/)
[[ I -ne 0 ]] && __="/${T2[*]}/" || __=/
;;
*)
[[ I -ne 0 ]] && __="/${T2[*]}" || __=/.
;;
esac
}
one good solution under a shell would be :
readlink -ev mypathname
It prints out the full path name with dots resolved.
Relative paths must be converted into absolute, it is NOT required to exist!
Neither symlinks should be resolved.
Try this one-line python solution:
abs_path=$(python -c "import os; print(os.path.abspath(\"$rel_path\"))")
in your example:
python -c "import os; print(os.path.abspath(\"/home/nohsib/dvc/../bop\"))"
returns /home/nohsib/bop
Try this (assuming your relative path is stored in the variable $rel_path):
echo "`cd $rel_path; pwd`"

Renaming a set of files to 001, 002,

I originally had a set of images of the form image_001.jpg, image_002.jpg, ...
I went through them and removed several. Now I'd like to rename the leftover files back to image_001.jpg, image_002.jpg, ...
Is there a Linux command that will do this neatly? I'm familiar with rename but can't see anything to order file names like this. I'm thinking that since ls *.jpg lists the files in order (with gaps), the solution would be to pass the output of that into a bash loop or something?
If I understand right, you have e.g. image_001.jpg, image_003.jpg, image_005.jpg, and you want to rename to image_001.jpg, image_002.jpg, image_003.jpg.
EDIT: This is modified to put the temp file in the current directory. As Stephan202 noted, this can make a significant difference if temp is on a different filesystem. To avoid hitting the temp file in the loop, it now goes through image*
i=1; temp=$(mktemp -p .); for file in image*
do
mv "$file" $temp;
mv $temp $(printf "image_%0.3d.jpg" $i)
i=$((i + 1))
done
A simple loop (test with echo, execute with mv):
I=1
for F in *; do
echo "$F" `printf image_%03d.jpg $I`
#mv "$F" `printf image_%03d.jpg $I` 2>/dev/null || true
I=$((I + 1))
done
(I added 2>/dev/null || true to suppress warnings about identical source and target files. If this is not to your liking, go with Matthew Flaschen's answer.)
Some good answers here already; but some rely on hiding errors which is not a good idea (that assumes mv will only error because of a condition that is expected - what about all the other reaons mv might error?).
Moreover, it can be done a little shorter and should be better quoted:
for file in *; do
printf -vsequenceImage 'image_%03d.jpg' "$((++i))"
[[ -e $sequenceImage ]] || \
mv "$file" "$sequenceImage"
done
Also note that you shouldn't capitalize your variables in bash scripts.
Try the following script:
numerate.sh
This code snipped should do the job:
./numerate.sh -d <your image folder> -b <start number> -L 3 -p image_ -s .jpg -o numerically -r
This does the reverse of what you are asking (taking files of the form *.jpg.001 and converting them to *.001.jpg), but can easily be modified for your purpose:
for file in *
do
if [[ "$file" =~ "(.*)\.([[:alpha:]]+)\.([[:digit:]]{3,})$" ]]
then
mv "${BASH_REMATCH[0]}" "${BASH_REMATCH[1]}.${BASH_REMATCH[3]}.${BASH_REMATCH[2]}"
fi
done
I was going to suggest something like the above using a for loop, an iterator, cut -f1 -d "_", then mv i i.iterator. It looks like it's already covered other ways, though.

Resources