Take the last part of the folder path in shell - linux

If you type pwd you get something like:
/home/username/Desctop/myfolder/
How to take the last part? The myfolder path.
This must be simple but I couldn't find easy solution in shell. I know how to take care of this in java but not in shell.
thanks

You're right--it's a quick command:
basename "$PWD"

Using basename $(pwd) are two useless and expensive forks.
echo "${PWD##*/}"
should do the trick completely in the shell without expensive forks (snag: for the root directory this is the empty string).

In Linux, there are a pair of commands, dirname and basename. dirname extracts all but the last part of a path, and basename extracts just the last part of a path.
In this case, using basename will do what you want:
basename $(pwd)

You can use basename for that, provided the last part is indeed a directory component (not a file):
$ basename /home/username/Desctop/myfolder/
myfolder

To extract the last part of a path, try using basename...
basename $(pwd);

function basename {
shopt -s extglob
__=${1%%+(/)}
[[ -z $__ ]] && __=/ || __=${__##*/}
}
basename "$PWD"
echo "$__"

Related

How to get only some simbols in bash script? [duplicate]

Given a string file path such as /foo/fizzbuzz.bar, how would I use bash to extract just the fizzbuzz portion of said string?
Here's how to do it with the # and % operators in Bash.
$ x="/foo/fizzbuzz.bar"
$ y=${x%.bar}
$ echo ${y##*/}
fizzbuzz
${x%.bar} could also be ${x%.*} to remove everything after a dot or ${x%%.*} to remove everything after the first dot.
Example:
$ x="/foo/fizzbuzz.bar.quux"
$ y=${x%.*}
$ echo $y
/foo/fizzbuzz.bar
$ y=${x%%.*}
$ echo $y
/foo/fizzbuzz
Documentation can be found in the Bash manual. Look for ${parameter%word} and ${parameter%%word} trailing portion matching section.
look at the basename command:
NAME="$(basename /foo/fizzbuzz.bar .bar)"
instructs it to remove the suffix .bar, results in NAME=fizzbuzz
Pure bash, done in two separate operations:
Remove the path from a path-string:
path=/foo/bar/bim/baz/file.gif
file=${path##*/}
#$file is now 'file.gif'
Remove the extension from a path-string:
base=${file%.*}
#${base} is now 'file'.
Using basename I used the following to achieve this:
for file in *; do
ext=${file##*.}
fname=`basename $file $ext`
# Do things with $fname
done;
This requires no a priori knowledge of the file extension and works even when you have a filename that has dots in it's filename (in front of it's extension); it does require the program basename though, but this is part of the GNU coreutils so it should ship with any distro.
The basename and dirname functions are what you're after:
mystring=/foo/fizzbuzz.bar
echo basename: $(basename "${mystring}")
echo basename + remove .bar: $(basename "${mystring}" .bar)
echo dirname: $(dirname "${mystring}")
Has output:
basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo
Pure bash way:
~$ x="/foo/bar/fizzbuzz.bar.quux.zoom";
~$ y=${x/\/*\//};
~$ echo ${y/.*/};
fizzbuzz
This functionality is explained on man bash under "Parameter Expansion". Non bash ways abound: awk, perl, sed and so on.
EDIT: Works with dots in file suffixes and doesn't need to know the suffix (extension), but doesn’t work with dots in the name itself.
Using basename assumes that you know what the file extension is, doesn't it?
And I believe that the various regular expression suggestions don't cope with a filename containing more than one "."
The following seems to cope with double dots. Oh, and filenames that contain a "/" themselves (just for kicks)
To paraphrase Pascal, "Sorry this script is so long. I didn't have time to make it shorter"
#!/usr/bin/perl
$fullname = $ARGV[0];
($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
print $basename . "\n";
In addition to the POSIX conformant syntax used in this answer,
basename string [suffix]
as in
basename /foo/fizzbuzz.bar .bar
GNU basename supports another syntax:
basename -s .bar /foo/fizzbuzz.bar
with the same result. The difference and advantage is that -s implies -a, which supports multiple arguments:
$ basename -s .bar /foo/fizzbuzz.bar /baz/foobar.bar
fizzbuzz
foobar
This can even be made filename-safe by separating the output with NUL bytes using the -z option, for example for these files containing blanks, newlines and glob characters (quoted by ls):
$ ls has*
'has'$'\n''newline.bar' 'has space.bar' 'has*.bar'
Reading into an array:
$ readarray -d $'\0' arr < <(basename -zs .bar has*)
$ declare -p arr
declare -a arr=([0]=$'has\nnewline' [1]="has space" [2]="has*")
readarray -d requires Bash 4.4 or newer. For older versions, we have to loop:
while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)
perl -pe 's/\..*$//;s{^.*/}{}'
If you can't use basename as suggested in other posts, you can always use sed. Here is an (ugly) example. It isn't the greatest, but it works by extracting the wanted string and replacing the input with the wanted string.
echo '/foo/fizzbuzz.bar' | sed 's|.*\/\([^\.]*\)\(\..*\)$|\1|g'
Which will get you the output
fizzbuzz
Beware of the suggested perl solution: it removes anything after the first dot.
$ echo some.file.with.dots | perl -pe 's/\..*$//;s{^.*/}{}'
some
If you want to do it with perl, this works:
$ echo some.file.with.dots | perl -pe 's/(.*)\..*$/$1/;s{^.*/}{}'
some.file.with
But if you are using Bash, the solutions with y=${x%.*} (or basename "$x" .ext if you know the extension) are much simpler.
The basename does that, removes the path. It will also remove the suffix if given and if it matches the suffix of the file but you would need to know the suffix to give to the command. Otherwise you can use mv and figure out what the new name should be some other way.
Combining the top-rated answer with the second-top-rated answer to get the filename without the full path:
$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz
If you want to keep just the filename with extension and strip the file path
$ x="myfile/hello/foo/fizzbuzz.bar"
$ echo ${x##*/}
$ fizzbuzz.bar
Explanation in Bash manual, see ${parameter##word}
You can use
mv *<PATTERN>.jar "$(basename *<PATTERN>.jar <PATTERN>.jar).jar"
For e.g:- I wanted to remove -SNAPSHOT from my file name. For that used below command
mv *-SNAPSHOT.jar "$(basename *-SNAPSHOT.jar -SNAPSHOT.jar).jar"

rename all files in folder through regular expression

I have a folder with lots of files which name has the following structure:
01.artist_name - song_name.mp3
I want to go through all of them and rename them using the regexp:
/^d+\./
so i get only :
artist_name - song_name.mp3
How can i do this in bash?
You can do this in BASH:
for f in [0-9]*.mp3; do
mv "$f" "${f#*.}"
done
Use the Perl rename utility utility. It might be installed on your version of Linux or easy to find.
rename 's/^\d+\.//' -n *.mp3
With the -n flag, it will be a dry run, printing what would be renamed, without actually renaming. If the output looks good, drop the -n flag.
Use 'sed' bash command to do so:
for f in *.mp3;
do
new_name="$(echo $f | sed 's/[^.]*.//')"
mv $f $new_name
done
...in this case, regular expression [^.].* matches everything before first period of a string.

copy files along with their (last) folders

I can find and copy all the files to a given folder using find -exec command.
But what I need to do is to find and copy all the files within a given path along with its folder in which it has been saved. So ....
/path/to/file/is/abc.txt
/another/file/is/here/xyz.txt
I need to copy these 2 files along with their path to the following folder:
/mysql/data/
The new file structure will look like this...
/mysql/data/is/abc.txt
/mysql/data/here/xyz.txt
This is done in order to avoid possible overwrite of duplicate file names. The last folder names will be unique but file names may be the same.
What is the best way to do this?
Here's a concise script with a rather long explanation* to accompany it.
for oldpath in $your_file_list; do
mv ${oldpath} /mysql/data${oldpath##$(dirname $(dirname $oldpath))}
done
How it works
The dirname utility removes everything up to and including the last forward slash (/) from a path. Invoking it twice will remove everything up to and including the second-to-last slash.
The idiom $(command with params) executes command with the parameters with params and returns the output.
The idiom ${var##prefix} returns the contents of the variable var with prefix removed.
Step-by-step Analysis
If oldpath is /path/to/file/is/abc.txt, then:
dirname $oldpath is /path/to/file/is
dirname $(dirname $oldpath) is /path/to/file
${oldpath##$(dirname $(dirname $oldpath))} is /is/abc.txt
which is the portion of the original path that will be appended to the new path.
* Elegant (adj.) software: any software that implements an algorithm, whose explanation is longer than the implementation itself.
You're going to have to script/program this solution.
Quick python example follows:
import os
import shutils
src_root = '/path/to/walk/'
dst_root = '/mysql/data/'
for root,dirs,files in os.walk(src_root):
for file in files:
dst_path = os.path.split(root)[1]
dst_path = os.path.join(dst_root, dst_path)
os.makedirs(dst_path)
src = os.path.join(root,i file)
dst = os.path.join(dst_path, file)
shutils.copyfile(srd, dst)
This might work for you:
a=/mysql/data
sed 's|.*\(/[^/]*/[^/]*\)|mv -v & '"$a"'\1|' file
mv -v /path/to/file/is/abc.txt /mysql/data/is/abc.txt
mv -v /another/file/is/here/xyz.txt /mysql/data/here/xyz.txt
Study the output and if all OK, then run:
sed 's|.*\(/[^/]*/[^/]*\)|mv -v & '"$a"'\1|' file | bash
If you want to copy a file first you need to create a directory for it. You can do that using single find command, but I'm not sure about efficiency of this solution?
#!/bin/bash
# $1 - destination
find $1 -type f -exec bash -c '
dest="$2"; # $2 is second argument passed to the script
dir=$(basename $(dirname $1));
mkdir $dest/$dir 2>/dev/null;
cp $1 "$dest/$dir/";
' -- {} $2 \; # {} = $1 in bash '..' and $2=$2
usage: ./copy copy_from copy_to
edit:
that looks better:
#!/bin/bash
dest=$2
from=$1
# copy_file from dest
copy_file() {
dir=$(basename $(dirname $from))
mkdir $dest/$dir
cp $from $dest/$dir
}
find $from -type f | while read file; do copy_file $file $dest; done

Recursively look for files with a specific extension

I'm trying to find all files with a specific extension in a directory and its subdirectories with my bash (Latest Ubuntu LTS Release).
This is what's written in a script file:
#!/bin/bash
directory="/home/flip/Desktop"
suffix="in"
browsefolders ()
for i in "$1"/*;
do
echo "dir :$directory"
echo "filename: $i"
# echo ${i#*.}
extension=`echo "$i" | cut -d'.' -f2`
echo "Erweiterung $extension"
if [ -f "$i" ]; then
if [ $extension == $suffix ]; then
echo "$i ends with $in"
else
echo "$i does NOT end with $in"
fi
elif [ -d "$i" ]; then
browsefolders "$i"
fi
done
}
browsefolders "$directory"
Unfortunately, when I start this script in terminal, it says:
[: 29: in: unexpected operator
(with $extension instead of 'in')
What's going on here, where's the error?
But this curly brace
find "$directory" -type f -name "*.in"
is a bit shorter than that whole thing (and safer - deals with whitespace in filenames and directory names).
Your script is probably failing for entries that don't have a . in their name, making $extension empty.
find {directory} -type f -name '*.extension'
Example: To find all csv files in the current directory and its sub-directories, use:
find . -type f -name '*.csv'
The syntax I use is a bit different than what #Matt suggested:
find $directory -type f -name \*.in
(it's one less keystroke).
Without using find:
du -a $directory | awk '{print $2}' | grep '\.in$'
Though using find command can be useful here, the shell itself provides options to achieve this requirement without any third party tools. The bash shell provides an extended glob support option using which you can get the file names under recursive paths that match with the extensions you want.
The extended option is extglob which needs to be set using the shopt option as below. The options are enabled with the -s support and disabled with he -u flag. Additionally you could use couple of options more i.e. nullglob in which an unmatched glob is swept away entirely, replaced with a set of zero words. And globstar that allows to recurse through all the directories
shopt -s extglob nullglob globstar
Now all you need to do is form the glob expression to include the files of a certain extension which you can do as below. We use an array to populate the glob results because when quoted properly and expanded, the filenames with special characters would remain intact and not get broken due to word-splitting by the shell.
For example to list all the *.csv files in the recursive paths
fileList=(**/*.csv)
The option ** is to recurse through the sub-folders and *.csv is glob expansion to include any file of the extensions mentioned. Now for printing the actual files, just do
printf '%s\n' "${fileList[#]}"
Using an array and doing a proper quoted expansion is the right way when used in shell scripts, but for interactive use, you could simply use ls with the glob expression as
ls -1 -- **/*.csv
This could very well be expanded to match multiple files i.e. file ending with multiple extension (i.e. similar to adding multiple flags in find command). For example consider a case of needing to get all recursive image files i.e. of extensions *.gif, *.png and *.jpg, all you need to is
ls -1 -- **/+(*.jpg|*.gif|*.png)
This could very well be expanded to have negate results also. With the same syntax, one could use the results of the glob to exclude files of certain type. Assume you want to exclude file names with the extensions above, you could do
excludeResults=()
excludeResults=(**/!(*.jpg|*.gif|*.png))
printf '%s\n' "${excludeResults[#]}"
The construct !() is a negate operation to not include any of the file extensions listed inside and | is an alternation operator just as used in the Extended Regular Expressions library to do an OR match of the globs.
Note that these extended glob support is not available in the POSIX bourne shell and its purely specific to recent versions of bash. So if your are considering portability of the scripts running across POSIX and bash shells, this option wouldn't be right.
find "$PWD" -type f -name "*.in"
There's a { missing after browsefolders ()
All $in should be $suffix
The line with cut gets you only the middle part of front.middle.extension. You should read up your shell manual on ${varname%%pattern} and friends.
I assume you do this as an exercise in shell scripting, otherwise the find solution already proposed is the way to go.
To check for proper shell syntax, without running a script, use sh -n scriptname.
To find all the pom.xml files in your current directory and print them, you can use:
find . -name 'pom.xml' -print
find $directory -type f -name "*.in"|grep $substring
for file in "${LOCATION_VAR}"/*.zip
do
echo "$file"
done

linux releative path to fullpath file name

I need a way of getting the fullpath name of a file on a linux shell script.
The full path may already be supplied or a relative file may be supplied.
afile.txt
/home/me/bfile.txt
to
/home/me/afile.txt
/home/me/bfile.txt
any ideas?
Use readlink(1).
readlink -f afile
Quick hack:
get_fn()
{
echo $(cd $(dirname $1); pwd)/$(basename $1)
}
But it can be costly.
If the directory will be the same, you can list the files in that directory in this way:
DIRECTORY=/some/directory
FILE_NAME="my-file-list"
for i in `ls -1 $DIRECTORY`
do
echo $i >> $FILE_NAME
done
Otherwise, you would use the FIND command in the How can I list files with their absolute path in linux?

Resources