1

I'm asking user to provide filename in Windows Batch, by using this command:

set /p my_filename=Enter filename for this project

How can I verify that %my_filename% contains valid Windows filename (not path) and re ask user if it does not?

EDIT The file in question does not yet exists when running the script (it will be created by script)

PiotrK
  • 4,210
  • 6
  • 45
  • 65

3 Answers3

3
@echo off
    setlocal enableextensions disabledelayedexpansion

:askFile

    rem Retrieve filename. On empty input ask again
    set /p "my_file=Enter filename for this project: " || goto :askFile

    rem Use delayed expansion to avoid problems with special characters
    setlocal enabledelayedexpansion

    rem Disable delimiters and end of line characters in for command
    for /f delims^=^ eol^= %%a in ("!my_file!") do (
        rem Cancel delayed expansion to avoid ! removal during expansion
        endlocal

        rem Until checked, we don't have a valid file
        set "my_file="

        rem Check we don't have a path, it is not a folder and the file exists
        if /i "%%~a"=="%%~nxa" if not exist "%%~a\" if exist "%%~a" set "my_file=%%~nxa"
    )

    rem If we don't have a file name (it was not valid) ask again
    if not defined my_file goto :askFile

    echo Selected file is "%my_file%"

edited to adapt to comments

@echo off
    setlocal enableextensions disabledelayedexpansion

:askFile

    rem Retrieve filename. On empty input ask again
    set /p "my_file=Enter filename for this project: " || goto :askFile

    rem See Naming Files, Paths, and Namespaces
    rem     https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx

    rem NOTE: From here, we will be enabling/disabling delayed expansion 
    rem       to avoid problems with special characters

    setlocal enabledelayedexpansion
    rem Ensure we do not have restricted characters in file name trying to use them as 
    rem delimiters and requesting the second token in the line
    for /f tokens^=2^ delims^=^<^>^:^"^/^\^|^?^*^ eol^= %%y in ("[!my_file!]") do (
        rem If we are here there is a second token, so, there is a special character
        echo Error : Non allowed characters in file name
        endlocal & goto :askFile
    )

    rem Check MAX_PATH (260) limitation
    set "my_temp_file=!cd!\!my_file!" & if not "!my_temp_file:~260!"=="" (
        echo Error : file name too long
        endlocal & goto :askFile
    )

    rem Check path inclusion, file name correction
    for /f delims^=^ eol^= %%a in ("!my_file!") do (
        rem Cancel delayed expansion to avoid ! removal during expansion
        endlocal

        rem Until checked, we don't have a valid file
        set "my_file="

        rem Check we don't have a path 
        if /i not "%%~a"=="%%~nxa" (
            echo Error : Paths are not allowed
            goto :askFile
        )

        rem Check it is not a folder 
        if exist "%%~nxa\" (
            echo Error : Folder with same name present 
            goto :askFile
        )

        rem ASCII 0-31 check. Check file name can be created
        2>nul ( >>"%%~nxa" type nul ) || (
            echo Error : File name is not valid for this file system
            goto :askFile
        )

        rem Ensure it was not a special file name by trying to delete the newly created file
        2>nul ( del /q /f /a "%%~nxa" ) || (
            echo Error : Reserved file name used
            goto :askFile
        )

        rem Everything was OK - We have a file name 
        set "my_file=%%~nxa"
    )

    echo Selected file is "%my_file%"
    goto :eof
MC ND
  • 69,615
  • 8
  • 84
  • 126
  • I run it, but it doesn't let me pass if the file in question does not exists -> see my clarification above – PiotrK Aug 20 '17 at 12:02
  • @PiotrK, file *may* not exist or file *must* not exist? Can it be overwritten if present? – MC ND Aug 20 '17 at 13:03
  • The file is written in newly created directory that it is always empty (if not, it is deleted and recreated). So the file will never exist apriori to calling the script – PiotrK Aug 20 '17 at 13:11
  • 1
    @PiotrK, answer updated. I've included checks for most of the usual problems (see included link). – MC ND Aug 20 '17 at 13:25
  • Thanks, that got it! – PiotrK Aug 20 '17 at 13:31
  • 1
    Just before or just after `echo Error : Reserved file name used` would you not need a `del /a /f "%%~nxa"` to remove the one previously `type`d there that failed without the attribute option? – Compo Aug 20 '17 at 13:32
  • @Compo, OP indicated that the code will be running in a newly created / empty folder. If the previous redirection failed, the `reserved` case is not reached. If the redirection worked and the file was created it can be deleted (in the indicated scenario) unless it is a reserved file name that allowed the redirection but can not be deleted (it is a *device*). I'm not saying it is not needed, I'm saying I don't see why it is needed (and I know sometimes I'm really blind). What am I missing? – MC ND Aug 20 '17 at 14:32
  • I thought that you were ensuring the newly created file you'd `type`d could be deleted, and throwing an error if it couldn't. Because you'd used `del` without the relevant options for 'special' files, the error would be thrown if by some miracle that newly created file had become special. In that scenario the newly created file would remain, hence the reason I suggested including the `del`ete command with the 'special' options to remove it. – Compo Aug 21 '17 at 09:47
  • @Compo, To be *miracle* aware I've changed the already present `del` command. Anyway, `del` command does not raise errorlevel when it can not remove a file. The test is just to handle the `con`, `lpt1`, .... special file names that can not be created (but can be written to) and that will make `del` command raise errorlevel when tried to being deleted. – MC ND Aug 21 '17 at 12:32
2
  • You could strip posible path with a for
  • Check with findstr Regular Expression for only allowed chars
  • Another findstr to exclude possible device names

:: Q:\Test\2017\08\20\SO_45780452.cmd
@Echo off
:loop
set "my_filename="
set /p "my_filename=Enter filename for this project: "

set "test="
for %%A in ("%my_filename%") Do  Set "test=%%~nxA"
If "%test%" neq "%my_filename%" (
    Echo no drive/path please
    Goto :loop
)

echo:%my_filename%|findstr /i "^[0-9A-Z.-_]*$" >Nul 2>&1 ||(
    Echo invalid chars
    goto :loop
)

:: check possible device name and reject
echo:%my_filename%|findstr /i "^aux$ ^con$ ^com[0-9]*$ ^lpt[0-9]*$ ^nul$ ^prn$" >Nul 2>&1 && (
    Echo invalid device name
    goto :loop
)
:: my_filename should be a tolerable name
  • It let my pass on names such as 'con'... :-( – PiotrK Aug 20 '17 at 12:03
  • For perfect answers you should have to pay ;-). You got the idea of findstr. See changed answer. –  Aug 20 '17 at 12:21
  • Right, but this is a very - I believe - common problem, that actually does not have solution here on Stack. So I wish the accepted answer would solve the issue for anyone who came here from Google ;-) – PiotrK Aug 20 '17 at 13:16
  • Btw. your answer operate on allowed character instead of disallowed -> For example national diacritic would be rejected – PiotrK Aug 20 '17 at 13:16
  • Oh gee, did you really expect others to do **your** task? I showed you a working example how you can solve your task. You may of course revert the logic or insert any other allowed chars.. Handling of any invalid char will need some escaping in the regex. –  Aug 20 '17 at 13:29
  • No, I do not; This is only smart part of my task, part that someone may have already solved; I was surprised that this was not a case. However I would like that future people get accepted answer that solve their problem too, instead of asking another question or wasting time for re-solving problem on their own. Consider this: if I adapt your partial answer to full answer and post it below, which one should I accept (for karma points), and which one would be the most useful to the community? :-) – PiotrK Aug 20 '17 at 13:40
1

Very elementary filename validity control:

echo %my_filename%| findstr /R /C:"*\.*"

There could be glitches with reserved DOS-times names (CON, PRN, NUL, etc..) and/or some special characters, so maybe you could simply try to store something into file under provided name, and than check if file was created successfully. Something like:

:getfilename
set /p my_filename=Enter filename for this project
echo A>%my_filename%
if not exist %my_filename% (echo Wrong filename & goto getfilename) else (del %my_filename% >NUL)

ALL UNTESTED!! Check if working directory is empty first.

user2956477
  • 1,208
  • 9
  • 17