Menu based shell script to delete files - shell

My requirement is to delete file based on it displayed. Following is code snippet where I listed files but when I select option it displays file and when I capture file name its not happening only getting key not VALUE($REPLY only displays key but not value). Can someone help me out.
#!/bin/bash
select list in $(ls *.tmp)
do
echo $list
echo Do you want to delete files ?
read userInput
echo "UserInput is :: " $userInput
echo "Reply is :: " $REPLY
if [ $userInput == $REPLY ] ; then
# rm $REPLY
echo 'Yes'
break
done
----OUTPUT-----
1) +~JF1905393034413613060.tmp
2) +~JF2032005334435574091.tmp
3) +~JF3116454937363220082.tmp
4) +~JF3334986634800781310.tmp
5) +~JF3651229840772890748.tmp
6) +~JF3882306323060007639.tmp
7) +~JF573641658479505435.tmp
8) +~JF6137053351660236007.tmp
9) +~JF6277682393160684532.tmp
10) +~JF6385610668752278364.tmp
11) +~JF6824954027739238354.tmp
12) +~JF7876557427734797684.tmp
#? 4
+~JF3334986634800781310.tmp
Do you want to delete files ?
y
UserInput is :: y
Reply is :: 4
No

Try this:
PS3="Pick a file number to delete (or Ctrl-C to quit): "
select f in *.tmp ; do echo rm "$f" ; done
Then remove the echo to make it actually delete files.

Related

Bash/sh: Move Folder + subfolder(s) reclusively rename files if they exist [duplicate]

This question already has answers here:
Extract filename and extension in Bash
(38 answers)
Closed 1 year ago.
I'm trying to create a bash script that will move all files recursively from a source folder to a target folder, and simply rename files if they already exist. Similar to the way M$ Windows does, when a file exists it auto-renames it with "<filemame> (X).<ext>", etc. except for ALL files.
I've create the below, which works fine for almost all scenarios except when a folder has a (.) period in its name and a file within that folder has no extension (no period in its name).
eg a folder-path-file such as: "./oldfolder/this.folder/filenamewithoutextension"
I get (incorrectly):
"./newfolder/this (1).folder/filenamewithoutextension"
if "./newfolder/this.folder/filenamewithoutextension" already exist in the target location (./newfolder),
instead of correctly naming the new file: "./oldfolder/this.folder/filenamewithoutextension (1)"
#!/bin/bash
source=$1 ; target=$2 ;
if [ "$source" != "" ] && [ "$target" != "" ] ; then
#recursive file search
find "$source" -type f -exec bash -c '
#setup variables
oldfile="$1" ; osource='"${source}"' ; otarget='"${target}"' ;
#set new target filename with target path
newfile="${oldfile/${osource}/${otarget}}" ;
#check if file already exists at target
[ -f "${newfile}" ] && {
#get the filename and fileextension for numbering - ISSUE HERE?
filename="${newfile%/}" ; newfileext="${newfile##*.}" ;
#compare filename and file extension for missing extension
if [ "$filename" == "$newfileext" ] ; then
#filename has no ext - perhaps fix the folder with a period issue here?
newfileext="" ;
else
newfileext=".$newfileext" ;
fi
#existing files counter
cnt=1 ; while [ -f "${newfile%.*} (${cnt})${newfileext}" ] ; do ((cnt+=1)); done
#set new filename with counter - New Name created here *** Needs re-work, as folder with a period = fail
newfile="${newfile%.*} (${cnt})${newfileext}";
}
#show mv command
echo "mv \"$oldfile\" \"${newfile}\""
' _ {} \;
else
echo "Requires source and target folders";
fi
I suspect the issue is, how to properly identify the filename and extension, found in this line:
filename="${newfile%/}" ; newfileext="${newfile##*.}" which doesn't identify a filename properly (files are always after the last /).
Any suggestion on how to make it work properly?
UPDATED: Just some completion notes - Issues fixes with:
Initially Splitting each full path filename: path - filename - (optional ext)
Reconstructing the full path filename: path - filename - counter - (optional ext)
fixed the file move to ensure directory structure exists with mkdir -p (mv does not create new folders if they do not exist in the target location).
Maybe you could try this instead?
filename="${newfile##*/}" ; newfileext="${filename#*.}"
The first pattern means: remove the longest prefix (in a greedy way) up to the last /.
The second one: remove the prefix up to the first dot (the greedy mode seems unnecessary here) − and as you already noted, in case the filename contains no dot, you will get newfileext == filename…
Example session:
newfile='./oldfolder/this.folder/filenamewithoutextension'
filename="${newfile##*/}"; newfileext="${filename#*.}"
printf "%s\n" "$filename"
#→ filenamewithoutextension
printf "%s\n" "$newfileext"
#→ filenamewithoutextension
newfile='./oldfolder/this.folder/file.tar.gz'
filename="${newfile##*/}"; newfileext="${filename#*.}"
printf "%s\n" "$filename"
#→ file.tar.gz
printf "%s\n" "$newfileext"
#→ tar.gz

How to warn before opening certain filetypes in vim?

It is easy to accidentally open a large binary or data file with vim when relying on command line autocomplete.
Is it possible to add an interactive warning when opening certain file types in vim?
For example, I'd like to add a warning when opening files without an extension:
> vim someBinary
Edit someBinary? [y/N]
Or maybe:
> vim someBinary
# vim buffer opens and displays warning about filetype,
# giving user a chance to quit before loading the file
This could be applied to a range of extensions, such as .pdf, .so, .o, .a, no extension, etc.
There is a related question on preventing vim from opening binary files, but it is primarily about modifying autocomplete to prevent accidentally opening the files in the first place.
Below is the solution I came up with, using vim autocommands with the BufReadCmd event. It's a lot of vimscript, but it's pretty robust. It issues a warning if the file being opened is a non-ascii file or has a blacklisted extension (.csv and .tsv for this example):
augroup bigfiles
" Clear the bigfiles group in case defined elsewhere
autocmd!
" Set autocommand to run before reading buffer
autocmd BufReadCmd * silent call PromptFileEdit()
augroup end
" Prompt user input if editing an existing file before reading
function! PromptFileEdit()
" Current file
let file = expand("%")
" Whether or not we should continue to open the file
let continue = 1
" Skip if file has an extension or is not readable
if filereadable(file) && (IsNonAsciiFile(file) || IsBlacklistedFile())
" Get response from user
let response = input('Are you sure you want to open "' . file . '"? [y/n]')
" Bail if response is a 'n' or contains a 'q'
if response ==? "n" || response =~ "q"
let continue = 0
if (winnr("$") == 1)
" Quit if it was the only buffer open
quit
else
" Close buffer if other buffers open
bdelete
endif
endif
endif
if continue == 1
" Edit the file
execute "e" file
" Run the remaining autocommands for the file
execute "doautocmd BufReadPost" file
endif
endfunction
" Return 1 if file is a non-ascii file, otherwise 0
function! IsNonAsciiFile(file)
let ret = 1
let fileResult = system('file ' . a:file)
" Check if file contains ascii or is empty
if fileResult =~ "ASCII" || fileResult =~ "empty" || fileResult =~ "UTF"
let ret = 0
endif
return ret
endfunction
" Return 1 if file is blacklisted, otherwise 0
function! IsBlacklistedFile()
let ret = 0
let extension = expand('%:e')
" List contains ASCII files that we don't want to open by accident
let blacklistExtensions = ['csv', 'tsv']
" Check if we even have an extension
if strlen(extension) == 0
let ret = 0
" Check if our extension is in the blacklisted extensions
elseif index(blacklistExtensions, extension) >= 0
let ret = 1
endif
return ret
endfunction
To read with syntax highlighting enabled, see this gist.
Maybe not super elegant, but I enjoyed learning some vimscript along the way.
I am not too experienced with vimscript so I'm sure there is room for improvements -- suggestions and alternative solutions welcome.
Note: This is not expected to work on Windows systems outside of WSL or Cygwin, due to calling file.
You can use a function like this
promptvim() {
grep -Fq "." <<< "$1" || read -p "Edit $1? [y/N]" && [[ $REPLY == "y" ]] || return
/usr/bin/vim "$1"
}
You can choose a different function name. When you use vim other scripts might fail.
EDIT:
When you like this construction (wrapper, not vim settings), you can make the function better with more tests:
promptvim() {
if [ $# -eq 0 ]; then
echo "arguments are missing"
return
fi
local maybe_dot=$(grep -Fq "." <<< "$1")
for file in $*; do
# skip tests when creating a new file
if [ -f "${file}" ]; then
maybe_dot=$(grep -F "." <<< "${file}")
if (( ${#maybe_dot} == 0 )); then
read -p "Edit ${file}? [y/N]"
# check default response variable REPLY
# Convert to lowercase and check other ways of confirming too
if [[ ! "${REPLY,,}" =~ ^(y|yes|j|ja|s|si|o|oui)$ ]]; then
continue
fi
fi
fi
echo /usr/bin/vim "${file}"
done
}
This still not covers all special cases. You might want to add support of the vim parameters on the commandline, check for an interactive session and think what you want with here documents.

Ksh - Script: List Files and store as variable

I'm using kshon an HP-UX box.
In a portion of my script, I want to list certain files (*.xml), have them numbered and have the user choose a file by typing the number and hitting enter. That filename will then be stored as a variable.
Example of output:
Please choose a file:
1) bar27.xml
2) foo1.xml
3) foobar4.xml
Then the user types in 1, 2, or 3 and hit enter. The file name chosen needs to be stored as a variable. So if the user chooses 2 the variable should contain foo1.xml.
I've come up with the following which works:
files=$(ls *.xml)
i=1
for j in $files
do
echo "$i) $j"
file[i]=$j
i=$(( i + 1 ))
done
echo "Choose an XML file from above to use:"
read v_CHOOSELIST
echo "File chosen: ${file[$v_CHOOSELIST]}"

How do i achieve "read -i" in a different way?

I Have been looking on the internet but without finding a solution (hard to know what to serach for). I want a way to show "current value" on an input. Similar to what read -e -p "something: " -i "this" $variable does but in Busybox sh. As that option only became available in Bash4 which is not default. I will have to solve it another way. Not sure if this is possible..
read -p "Select by entering an number: " ANS
case $ANS in
1 ) ANS="%R | %a %d %b" ;;
2 ) ANS="%R, %a %d %b" ;;
3 ) read -p "Enter a custom string (ex. %R): " ANS ;;
4 ) ANS="%R" ;;
* ) read -p "Just answer with a number between 1 to 4. Aborting!" end; exit 0 ;;
esac
What i want is that case #3 will show the current value of the file. Ex. If the file has %R as current value. Then that is what i want it to show on input for user to interactively change it.
Until you can give us examples of what you want the programme to take as an 'input' and then give as an 'output', I suggest you read TLPD's section on 'read'

BASH scripts : whiptail file select

I've found a great little program that will allow me to add user friendly GUI's to my Bash Scripts;
whiptail
However the whiptail man page isn't all that helpful and doesn't provide any examples. After doing some google searches I understand how to create a simple yes/no menu using whiptail:
#! /bin/bash
# http://archives.seul.org/seul/project/Feb-1998/msg00069.html
if (whiptail --title "PPP Configuration" --backtitle "Welcome to SEUL" --yesno "
Do you want to configure your PPP connection?" 10 40 )
then
echo -e "\nWell, you better get busy!\n"
elif (whiptail --title "PPP Configuration" --backtitle "Welcome to
SEUL" --yesno " Are you sure?" 7 40)
then
echo -e "\nGood, because I can't do that yet!\n"
else
echo -e "\nToo bad, I can't do that yet\n"
fi
But what I would really like to build a file select menu using whiptail to replace some old code I have in a few different backup/restore bash scripts I have:
#!/bin/bash
#This script allows you to select a file ending in the .tgz extension (in the current directory)
echo "Please Select the RESTORE FILE you would like to restore: "
select RESTOREFILE in *.tgz; do
break #Nothing
done
echo "The Restore File you selected was: ${RESTOREFILE}"
I assume this has to be done via the '--menu' option of whiptail, but I am not sure how to go about it?
Any pointers?
Or can you point me in the direction of some whiptail examples?
Build an array of file names and menu select tags:
i=0
s=65 # decimal ASCII "A"
for f in *.tgz
do
# convert to octal then ASCII character for selection tag
files[i]=$(echo -en "\0$(( $s / 64 * 100 + $s % 64 / 8 * 10 + $s % 8 ))")
files[i+1]="$f" # save file name
((i+=2))
((s++))
done
A method like this will work even if there are filenames with spaces. If the number of files is large, you may have to devise another tag strategy.
Using alpha characters for the tags lets you press a letter to jump to the item. Numeric tags don't seem to do that. If you don't need that behavior, then you can eliminate some complexity.
Display the menu:
whiptail --backtitle "Welcome to SEUL" --title "Restore Files" \
--menu "Please select the file to restore" 14 40 6 "${files[#]}"
If the exit code is 255, the dialog was canceled.
if [[ $? == 255 ]]
then
do cancel stuff
fi
To catch the selection in a variable, use this structure (substitute your whiptail command for "whiptail-command"):
result=$(whiptail-command 2>&1 >/dev/tty)
Or
result=$(whiptail-command 3>&2 2>&1 1>&3-)
The variable $result will contain a letter of the alphabet that corresponds to a file in the array. Unfortunately, Bash prior to version 4 doesn't support associative arrays. You can calculate the index into the array of the file from the letter like this (notice the "extra" single quote):
((index = 2 * ( $( printf "%d" "'$result" ) - 65 ) + 1 ))
Example:
Welcome to SEUL
┌──────────┤ Restore Files ├───────────┐
│ Please select the file to restore │
│ │
│ A one.tgz ↑ │
│ B two.tgz ▮ │
│ C three.tgz ▒ │
│ D another.tgz ▒ │
│ E more.tgz ▒ │
│ F sp ac es.tgz ↓ │
│ │
│ │
│ <Ok> <Cancel> │
│ │
└──────────────────────────────────────┘
Whiptail is a lightweight reimplementation of the most popular features of dialog, using the Newt library. I did a quick check, and many features in Whiptail seem to behave like their counterparts in dialog. So, a dialog tutorial should get you started. You can find one here but Google is your friend of course. On the other hand, the extended example probably contains a lot of inspiration for your problem.
This function is part of my function repository for whiptail
# ----------------------------------------------------------------------
# File selection dialog
#
# Arguments
# 1 Dialog title
# 2 Source path to list files and directories
# 3 File mask (by default *)
# 4 "yes" to allow go back in the file system.
#
# Returns
# 0 if a file was selected and loads the FILE_SELECTED variable
# with the selected file.
# 1 if the user cancels.
# ----------------------------------------------------------------------
function dr_file_select
{
local TITLE=${1:-$MSG_INFO_TITLE}
local LOCAL_PATH=${2:-$(pwd)}
local FILE_MASK=${3:-"*"}
local ALLOW_BACK=${4:-no}
local FILES=()
[ "$ALLOW_BACK" != "no" ] && FILES+=(".." "..")
# First add folders
for DIR in $(find $LOCAL_PATH -maxdepth 1 -mindepth 1 -type d -printf "%f " 2> /dev/null)
do
FILES+=($DIR "folder")
done
# Then add the files
for FILE in $(find $LOCAL_PATH -maxdepth 1 -type f -name "$FILE_MASK" -printf "%f %s " 2> /dev/null)
do
FILES+=($FILE)
done
while true
do
FILE_SELECTED=$(whiptail --clear --backtitle "$BACK_TITLE" --title "$TITLE" --menu "$LOCAL_PATH" 38 80 30 ${FILES[#]} 3>&1 1>&2 2>&3)
if [ -z "$FILE_SELECTED" ]; then
return 1
else
if [ "$FILE_SELECTED" = ".." ] && [ "$ALLOW_BACK" != "no" ]; then
return 0
elif [ -d "$LOCAL_PATH/$FILE_SELECTED" ] ; then
if dr_file_select "$TITLE" "$LOCAL_PATH/$FILE_SELECTED" "$FILE_MASK" "yes" ; then
if [ "$FILE_SELECTED" != ".." ]; then
return 0
fi
else
return 1
fi
elif [ -f "$LOCAL_PATH/$FILE_SELECTED" ] ; then
FILE_SELECTED="$LOCAL_PATH/$FILE_SELECTED"
return 0
fi
fi
done
}
The use is simple
if dr_file_select "Please, select a file" /home/user ; then
echo "File Selected: \"$FILE_SELECTED\"."
else
echo "Cancelled!"
fi
I've tried following, which worked:
whiptail --title "PPP Config" --backtitle "Welcome to SEUL" --menu YourTitle 20 80 10 `for x in $(ls -1 *.tgz); do echo $x "-"; done`
you might change this into a multiple-liner as well, i've added checking for empty list:
MYLIST=`for x in $(ls -1 *.tgz); do echo $x "-"; done`
WC=`echo $MYLIST | wc -l`
if [[WC -ne 0]]; then
whiptail --title "PPP Config" --backtitle "Welcome to SEUL" --menu YourTitle 20 80 10 $MYLIST
fi
you need to adjust the numbers in order to get a cleaninterface. And you may replace the "-" by anything else if you want to. But if you don't, you will see 2 entries per line.
By the way: The selected entry is printed onto stderr.
This could need some more improving, but for a basic idea I think it's enough.
This seems to be one of the top results when you search for whiptail, and none of the previous results worked for me. This is what I wound up using:
#! /bin/bash
shopt -s nullglob
dir=`pwd`
cd /path/to/files
arr=(*.tgz)
for ((i=0; i<${#arr[#]}; i++)); do j=$((2*$i+2)); a[j]="${arr[$i]}"; a[j+1]=""; done
a[0]=""
# Next line has extra spaces at right to try to center it:
a[1]="Please make a selection from the files below "
result=$(whiptail --ok-button "OK button text" --cancel-button "Cancel Button Text" --title "Title Text" --backtitle "Text at upper left corner of page" --menu "Menu Text (not used??)" 30 160 24 "${a[#]}" 2>&1 >/dev/tty)
if [[ $? = 0 ]]
then
# ge 5 in next line should be length of file extension including . character, plus 1
[ ${#result} -ge 5 ] && outfile="/path/to/files/$result" || echo "Selection not made"
fi
cd "$dir"
$result will be empty if no valid selection was made. I added a dummy selection at the top of the list that returns an empty string as a result, so that you won't accidentally select the wrong file by accidentally hitting Enter right after the menu comes up. If you don't want that, then in the "for" line remove the +2 in "do j=$((2*$i+2))" and also the following two lines that set a[0] and a[1] explicitly.
The confusing thing about whiptail is that when reading from an array in a situation like this it expects two data items per line, both of which are displayed, the first being the result you want returned if the line is expected (which in some situations might be a letter or a number) and the second being whatever descriptive text you may want. That's why for the first line I use a[0] to give an empty string as the result, and a[1] as the descriptive text, but from there on the first item in the pair contains the filename (which is what I actually want returned) and the second is an empty string, since I don't want to display any text other than the filename on those lines.
Also a previous post said whiptail returned an error code of 255 if the cancel button was pressed, but that was not the case for the version I have - it returns 1. So I just test for an error code of 0 and if it is I assume it may be a valid entry, then I test for a valid string length (more than just the number of characters in the file extension, including the . character) to be sure.

Resources