ไฟล์แบตช์ - จำนวนอาร์กิวเมนต์บรรทัดคำสั่ง


100

เพียงแค่แปลงเชลล์สคริปต์บางส่วนเป็นไฟล์แบตช์และมีสิ่งหนึ่งที่ดูเหมือนว่าฉันไม่สามารถหาได้ ... และนั่นคือการนับจำนวนอาร์กิวเมนต์บรรทัดคำสั่งง่ายๆ

เช่น. ถ้าคุณมี:

myapp foo bar

ในเชลล์:

  • $ # -> 2
  • $ * -> แถบ foo
  • $ 0 -> myapp
  • $ 1 -> ฟู
  • $ 2 -> บาร์

ในชุด

  • ?? -> 2 <---- คำสั่งอะไร?!
  • % * -> แถบฟู
  • % 0 -> myapp
  • % 1 -> ฟู
  • % 2 -> บาร์

ดังนั้นฉันจึงมองไปรอบ ๆ และฉันมองผิดจุดหรือฉันตาบอด แต่ดูเหมือนจะหาวิธีนับจำนวนอาร์กิวเมนต์บรรทัดคำสั่งไม่ได้

มีคำสั่งคล้ายกับ "$ #" ของเชลล์สำหรับไฟล์แบตช์หรือไม่

ปล. สิ่งที่ใกล้เคียงที่สุดที่ฉันพบคือการวนซ้ำผ่าน% 1s และใช้ 'shift' แต่ฉันต้องอ้างอิง% 1,% 2 และอื่น ๆ ในสคริปต์ในภายหลังดังนั้นจึงไม่ดี


สตริงของคุณคือ2 myapp foo barอะไร?
PsychoData

2
คำแนะนำ: อย่าแปลง sh เป็น BAT ให้ดาวน์โหลด cygwin แทนและใช้แทน กล่าวคือสคริปต์ sh ของคุณจะทำงานบนเครื่อง windows และคุณจะไม่ต้องแปลทุก sh เป็น BAT!
Marty McGowan

คำตอบ:


107

Googling a bit ให้ผลลัพธ์ต่อไปนี้จากwikibooks :

set argC=0
for %%x in (%*) do Set /A argC+=1

echo %argC%

ดูเหมือนว่า cmd.exe จะพัฒนาขึ้นเล็กน้อยจาก DOS สมัยก่อน :)


7
โปรดทราบว่าตัวแปรนี้forใช้ได้เฉพาะกับอาร์กิวเมนต์ที่ดูเหมือนชื่อไฟล์เท่านั้นไม่ใช่สตริงตัวเลือกเช่น-?. การใช้อัญประกาศ ( for %%i in ("%*") ...) ใช้ได้กับอาร์กิวเมนต์เช่นเดียวกับ-?แต่ล้มเหลวอีกครั้งสำหรับอาร์กิวเมนต์ที่ยกมาเนื่องจากอัญประกาศซ้อนกัน วิธีเดียวที่แข็งแกร่งดูเหมือนจะเกี่ยวข้องกับshift...
เฟอร์ดินานด์เบเยอร์

1
MyBatchFile "*.*" "Abc"รายงานว่า argC == 9. - โหวตลดลง
BrainSlugs83

ได้ทดสอบอาร์กิวเมนต์นี้ถึง 2,500 อาร์กิวเมนต์เป็นฟังก์ชันและใช้เพื่อกำหนดอาร์เรย์ที่มีขนาดเท่ากัน อย่าถามฉันว่าทำไม ส่วนใหญ่เพียงแค่เรียนรู้ว่าแบทช์มีความสามารถอะไร
T3RR0R

45

คุณมักจะจัดการกับอาร์กิวเมนต์จำนวนมากด้วยตรรกะประเภทนี้:

IF "%1"=="" GOTO HAVE_0
IF "%2"=="" GOTO HAVE_1
IF "%3"=="" GOTO HAVE_2

เป็นต้น

หากคุณมีข้อโต้แย้งมากกว่า 9 ข้อแสดงว่าคุณกำลังสับสนกับแนวทางนี้ มีแฮ็กต่างๆสำหรับการสร้างเคาน์เตอร์ซึ่งคุณสามารถพบได้ที่นี่แต่ขอเตือนว่าสิ่งเหล่านี้ไม่เหมาะสำหรับคนใจเสาะ


6
คุณยังสามารถใช้shiftเพื่อนับได้มากกว่า 9 ... โดยไม่ต้องมีโค้ด 10 บรรทัดที่ดูเท่าเทียมกัน
โจอี้

19

ฟังก์ชัน:getargcด้านล่างนี้อาจเป็นสิ่งที่คุณกำลังมองหา

@echo off
setlocal enableextensions enabledelayedexpansion
call :getargc argc %*
echo Count is %argc%
echo Args are %*
endlocal
goto :eof

:getargc
    set getargc_v0=%1
    set /a "%getargc_v0% = 0"
:getargc_l0
    if not x%2x==xx (
        shift
        set /a "%getargc_v0% = %getargc_v0% + 1"
        goto :getargc_l0
    )
    set getargc_v0=
    goto :eof

โดยทั่วไปแล้วจะวนซ้ำหนึ่งครั้งในรายการ (ซึ่งเป็นฟังก์ชันเฉพาะของฟังก์ชันดังนั้นการเปลี่ยนแปลงจะไม่ส่งผลกระทบต่อรายการในโปรแกรมหลัก) โดยนับจนกว่าจะหมด

นอกจากนี้ยังใช้เคล็ดลับดีๆโดยส่งชื่อของตัวแปร return ที่ฟังก์ชันกำหนด

โปรแกรมหลักแสดงให้เห็นถึงวิธีการเรียกมันและสะท้อนข้อโต้แย้งในภายหลังเพื่อให้แน่ใจว่าไม่มีใครแตะต้อง:

C:\Here> xx.cmd 1 2 3 4 5
    Count is 5
    Args are 1 2 3 4 5
C:\Here> xx.cmd 1 2 3 4 5 6 7 8 9 10 11
    Count is 11
    Args are 1 2 3 4 5 6 7 8 9 10 11
C:\Here> xx.cmd 1
    Count is 1
    Args are 1
C:\Here> xx.cmd
    Count is 0
    Args are
C:\Here> xx.cmd 1 2 "3 4 5"
    Count is 3
    Args are 1 2 "3 4 5"

คุณจะแก้ไขโฟลว์ตามนี้ได้อย่างไร (อาจเป็นคำถามที่แตกต่างออกไป แต่ฉันคิดว่าตัวอย่างสั้น ๆ ก็สะดวกมากเช่นกัน!)
n611x007

@ n611x007 ซึ่งecho Count is %argc%คุณจะใส่รหัสของคุณเองที่สามารถตรวจสอบว่าจำนวนนั้นเหมาะสมหรือไม่จากนั้นจึงไปต่อ
kenny


6

หากจำนวนอาร์กิวเมนต์ควรเป็นจำนวนที่แน่นอน (น้อยกว่าหรือเท่ากับ 9) นี่เป็นวิธีง่ายๆในการตรวจสอบ:

if "%2" == "" goto args_count_wrong
if "%3" == "" goto args_count_ok

:args_count_wrong
echo I need exactly two command line arguments
exit /b 1

:args_count_ok

2

หลีกเลี่ยงการใช้อย่างใดอย่างหนึ่งshiftหรือforรอบโดยเสียขนาดและความสามารถในการอ่าน

@echo off
setlocal EnableExtensions EnableDelayedExpansion
set /a arg_idx=1
set "curr_arg_value="
:loop1
if !arg_idx! GTR 9 goto :done
set curr_arg_label=%%!arg_idx!
call :get_value curr_arg_value !curr_arg_label!
if defined curr_arg_value (
  echo/!curr_arg_label!: !curr_arg_value!
  set /a arg_idx+=1
  goto :loop1
)
:done
set /a cnt=!arg_idx!-1
echo/argument count: !cnt!
endlocal
goto :eof

:get_value
(
  set %1=%2
)

เอาท์พุต:

count_cmdline_args.bat testing more_testing arg3 another_arg

%1: testing
%2: more_testing
%3: arg3
%4: another_arg
argument count: 4

แก้ไข: "เคล็ดลับ" ที่ใช้ในที่นี้เกี่ยวข้องกับ:

  1. การสร้างสตริงที่แสดงถึงตัวแปรอาร์กิวเมนต์บรรทัดคำสั่งที่ประเมินในปัจจุบัน (เช่น "% 1", "% 2" เป็นต้น) โดยใช้สตริงที่มีอักขระเปอร์เซ็นต์ ( %%) และตัวแปรตัวนับarg_idxในการวนซ้ำแต่ละลูป

  2. curr_arg_labelการจัดเก็บสตริงที่เป็นตัวแปร

  3. ผ่านทั้งสตริง ( !curr_arg_label!) และชื่อของตัวแปรผลตอบแทน ( curr_arg_value) get_valueไปยังโปรแกรมย่อยดั้งเดิม

  4. ในโปรแกรมย่อยค่าอาร์กิวเมนต์แรก ( %1) จะถูกใช้ทางด้านซ้ายของการกำหนด ( set) และค่าอาร์กิวเมนต์ที่สองของ ( %2) ทางด้านขวา อย่างไรก็ตามเมื่อส่งผ่านอาร์กิวเมนต์ของโปรแกรมย่อยที่สองจะถูกแก้ไขเป็นค่าของอาร์กิวเมนต์บรรทัดคำสั่งของโปรแกรมหลักโดยตัวแปลคำสั่ง นั่นคือสิ่งที่ส่งผ่านไม่ใช่ตัวอย่างเช่น "% 4" แต่ค่าใดก็ตามที่ตัวแปรอาร์กิวเมนต์บรรทัดคำสั่งที่สี่มี ("another_arg" ในตัวอย่างการใช้งาน)

  5. จากนั้นตัวแปรที่กำหนดให้กับโปรแกรมย่อยเป็นตัวแปร return ( curr_arg_value) จะถูกทดสอบว่าไม่มีการกำหนดซึ่งจะเกิดขึ้นหากไม่มีอาร์กิวเมนต์บรรทัดคำสั่งที่ประเมินในปัจจุบัน ในขั้นต้นนี่เป็นการเปรียบเทียบค่าของตัวแปรส่งคืนที่ห่อด้วยวงเล็บเหลี่ยมกับวงเล็บเหลี่ยมว่าง (ซึ่งเป็นวิธีเดียวที่ฉันรู้เกี่ยวกับการทดสอบโปรแกรมหรืออาร์กิวเมนต์โปรแกรมย่อยซึ่งอาจมีเครื่องหมายคำพูดและเป็นส่วนที่เหลือจากการทดลองและข้อผิดพลาด) แต่ ได้รับการแก้ไขตั้งแต่ตอนนี้ว่าเป็นอย่างไร


1
แม้ว่ารหัสนี้อาจตอบคำถามได้ แต่การให้บริบทเพิ่มเติมเกี่ยวกับวิธีการและ / หรือเหตุผลในการแก้ปัญหาจะช่วยเพิ่มมูลค่าในระยะยาวของคำตอบ
thewaywewere

ขอบคุณมากฉันลืมไปว่าสำหรับคนจำนวนมาก (ซึ่งน่าจะเป็นส่วนใหญ่) ควรมีคำอธิบายข้อความแทนรหัส "อธิบายตัวเอง" ดีกว่า
fen-fire

@ fen-fire ยกเว้นว่ารหัสจะไม่สามารถอธิบายได้ด้วยตนเอง
Loduwijk

1

คำตอบล่าสุดคือสองปีที่แล้ว แต่ฉันต้องการเวอร์ชันสำหรับอาร์กิวเมนต์บรรทัดคำสั่งมากกว่าเก้าข้อ อาจจะเป็นอีกแบบก็ได้ ...

@echo off
setlocal

set argc_=1
set arg0_=%0
set argv_=

:_LOOP
set arg_=%1
if defined arg_ (
  set arg%argc_%_=%1
  set argv_=%argv_% %1
  set /a argc_+=1
  shift
  goto _LOOP
)
::dont count arg0
set /a argc_-=1
echo %argc_% arg(s)

for /L %%i in (0,1,%argc_%) do (
  call :_SHOW_ARG arg%%i_ %%arg%%i_%%
)

echo converted to local args
call :_LIST_ARGS %argv_%
exit /b


:_LIST_ARGS
setlocal
set argc_=0
echo arg0=%0

:_LOOP_LIST_ARGS
set arg_=%1
if not defined arg_ exit /b
set /a argc_+=1
call :_SHOW_ARG arg%argc_% %1
shift
goto _LOOP_LIST_ARGS


:_SHOW_ARG
echo %1=%2
exit /b

การแก้ปัญหาคือ 19 บรรทัดแรกและแปลงอาร์กิวเมนต์ทั้งหมดเป็นตัวแปรในลักษณะ c-like สิ่งอื่น ๆ ทั้งหมดเพียงแค่ตรวจสอบผลลัพธ์และแสดงการแปลงเป็น args ในเครื่อง คุณสามารถอ้างอิงอาร์กิวเมนต์ตามดัชนีในฟังก์ชันใดก็ได้


0

วิธีแก้ปัญหาที่มีประสิทธิภาพคือการมอบหมายการนับให้กับรูทีนย่อยที่เรียกใช้ด้วยcall; รูทีนย่อยใช้gotoคำสั่งเพื่อจำลองลูปซึ่งshiftใช้เพื่อใช้อาร์กิวเมนต์ (รูทีนย่อยเท่านั้น) ซ้ำ ๆ :

@echo off
setlocal

:: Call the argument-counting subroutine with all arguments received,
:: without interfering with the ability to reference the arguments
:: with %1, ... later.
call :count_args %*

:: Print the result.
echo %ReturnValue% argument(s) received.

:: Exit the batch file.
exit /b

:: Subroutine that counts the arguments given.
:: Returns the count in %ReturnValue%
:count_args
  set /a ReturnValue = 0
  :count_args_for

    if %1.==. goto :eof

    set /a ReturnValue += 1

    shift
  goto count_args_for
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.